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 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_BEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 unsigned int staincolor[2];
121 particleeffectinfo_t;
123 #define MAX_PARTICLEEFFECTNAME 256
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 #define MAX_PARTICLEEFFECTINFO 4096
128 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
130 static int particlepalette[256];
132 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
133 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
134 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
135 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
136 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
137 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
138 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
139 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
140 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
141 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
142 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
143 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
144 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
145 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
146 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
147 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
148 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
149 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
150 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
151 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
152 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
153 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
154 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
155 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
156 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
157 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
158 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
159 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
160 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
161 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
162 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
163 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
166 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
167 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
168 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
170 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
172 // texture numbers in particle font
173 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
174 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
175 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
176 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
177 static const int tex_rainsplash = 32;
178 static const int tex_particle = 63;
179 static const int tex_bubble = 62;
180 static const int tex_raindrop = 61;
181 static const int tex_beam = 60;
183 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
184 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
185 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
186 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
187 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
188 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
189 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
190 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
191 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
192 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
193 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
194 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
195 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
196 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
197 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
198 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
199 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
200 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
201 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
202 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
203 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
204 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
205 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
208 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
214 particleeffectinfo_t *info = NULL;
215 const char *text = textstart;
217 effectinfoindex = -1;
218 for (linenumber = 1;;linenumber++)
221 for (arrayindex = 0;arrayindex < 16;arrayindex++)
222 argv[arrayindex][0] = 0;
225 if (!COM_ParseToken_Simple(&text, true, false))
227 if (!strcmp(com_token, "\n"))
231 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
237 #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;}
238 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
239 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
240 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
241 #define readfloat(var) checkparms(2);var = atof(argv[1])
242 if (!strcmp(argv[0], "effect"))
247 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
249 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
252 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
254 if (particleeffectname[effectnameindex][0])
256 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
261 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
265 // if we run out of names, abort
266 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
268 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
271 info = particleeffectinfo + effectinfoindex;
272 info->effectnameindex = effectnameindex;
273 info->particletype = pt_alphastatic;
274 info->blendmode = particletype[info->particletype].blendmode;
275 info->orientation = particletype[info->particletype].orientation;
276 info->tex[0] = tex_particle;
277 info->tex[1] = tex_particle;
278 info->color[0] = 0xFFFFFF;
279 info->color[1] = 0xFFFFFF;
283 info->alpha[1] = 256;
284 info->alpha[2] = 256;
285 info->time[0] = 9999;
286 info->time[1] = 9999;
287 VectorSet(info->lightcolor, 1, 1, 1);
288 info->lightshadow = true;
289 info->lighttime = 9999;
290 info->stretchfactor = 1;
291 info->staincolor[0] = -1;
292 info->staincolor[1] = -1;
293 info->staintex[0] = -1;
294 info->staintex[1] = -1;
296 else if (info == NULL)
298 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
301 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
302 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
303 else if (!strcmp(argv[0], "type"))
306 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
307 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
308 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
309 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
310 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
311 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
312 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
313 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
314 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
315 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
316 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
317 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
318 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
319 info->blendmode = particletype[info->particletype].blendmode;
320 info->orientation = particletype[info->particletype].orientation;
322 else if (!strcmp(argv[0], "blend"))
325 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
326 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
327 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
328 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
330 else if (!strcmp(argv[0], "orientation"))
333 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
334 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
335 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
336 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_BEAM;
337 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
339 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
340 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
341 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
342 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
343 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
344 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
345 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
346 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
347 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
348 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
349 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
350 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
351 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
352 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
353 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
354 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
355 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
356 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
357 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
358 else if (!strcmp(argv[0], "lightshadow")) {readint(info->lightshadow);}
359 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
360 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
361 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
362 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
363 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
364 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
365 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
366 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = -1; info->staincolor[1] = -1;}
368 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
377 int CL_ParticleEffectIndexForName(const char *name)
380 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
381 if (!strcmp(particleeffectname[i], name))
386 const char *CL_ParticleEffectNameForIndex(int i)
388 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
390 return particleeffectname[i];
393 // MUST match effectnameindex_t in client.h
394 static const char *standardeffectnames[EFFECT_TOTAL] =
418 "TE_TEI_BIGEXPLOSION",
434 void CL_Particles_LoadEffectInfo(void)
437 unsigned char *filedata;
438 fs_offset_t filesize;
439 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
440 memset(particleeffectname, 0, sizeof(particleeffectname));
441 for (i = 0;i < EFFECT_TOTAL;i++)
442 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
443 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
446 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
456 void CL_ReadPointFile_f (void);
457 void CL_Particles_Init (void)
459 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)");
460 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
462 Cvar_RegisterVariable (&cl_particles);
463 Cvar_RegisterVariable (&cl_particles_quality);
464 Cvar_RegisterVariable (&cl_particles_alpha);
465 Cvar_RegisterVariable (&cl_particles_size);
466 Cvar_RegisterVariable (&cl_particles_quake);
467 Cvar_RegisterVariable (&cl_particles_blood);
468 Cvar_RegisterVariable (&cl_particles_blood_alpha);
469 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
470 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
471 Cvar_RegisterVariable (&cl_particles_explosions_shell);
472 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
473 Cvar_RegisterVariable (&cl_particles_rain);
474 Cvar_RegisterVariable (&cl_particles_snow);
475 Cvar_RegisterVariable (&cl_particles_smoke);
476 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
477 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
478 Cvar_RegisterVariable (&cl_particles_sparks);
479 Cvar_RegisterVariable (&cl_particles_bubbles);
480 Cvar_RegisterVariable (&cl_particles_visculling);
481 Cvar_RegisterVariable (&cl_decals);
482 Cvar_RegisterVariable (&cl_decals_visculling);
483 Cvar_RegisterVariable (&cl_decals_time);
484 Cvar_RegisterVariable (&cl_decals_fadetime);
487 void CL_Particles_Shutdown (void)
491 // list of all 26 parameters:
492 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
493 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
494 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
495 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_BEAM)
496 // palpha - opacity of particle as 0-255 (can be more than 255)
497 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
498 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
499 // pgravity - how much effect gravity has on the particle (0-1)
500 // 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
501 // px,py,pz - starting origin of particle
502 // pvx,pvy,pvz - starting velocity of particle
503 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
504 // blendmode - one of the PBLEND_ values
505 // orientation - one of the PARTICLE_ values
506 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
507 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
508 static particle_t *CL_NewParticle(unsigned short ptypeindex, 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, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex)
513 if (!cl_particles.integer)
515 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
516 if (cl.free_particle >= cl.max_particles)
519 lifetime = palpha / min(1, palphafade);
520 part = &cl.particles[cl.free_particle++];
521 if (cl.num_particles < cl.free_particle)
522 cl.num_particles = cl.free_particle;
523 memset(part, 0, sizeof(*part));
524 part->typeindex = ptypeindex;
525 part->blendmode = blendmode;
526 part->orientation = orientation;
527 l2 = (int)lhrandom(0.5, 256.5);
529 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
530 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
531 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
532 part->staintexnum = staintex;
533 if(staincolor1 >= 0 && staincolor2 >= 0)
535 l2 = (int)lhrandom(0.5, 256.5);
537 r = (((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0xFF00) & 0xFF;
538 g = (((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0xFF00) & 0xFF;
539 b = (((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[1]) / 0xFF00) & 0xFF;
540 part->staincolor = (255 - r) * 65536 + (255 - g) * 256 + (255 - b); // inverted, as decals draw in inverted color (subtractive)!
543 part->staincolor = -1;
546 part->sizeincrease = psizeincrease;
547 part->alpha = palpha;
548 part->alphafade = palphafade;
549 part->gravity = pgravity;
550 part->bounce = pbounce;
551 part->stretch = stretch;
553 part->org[0] = px + originjitter * v[0];
554 part->org[1] = py + originjitter * v[1];
555 part->org[2] = pz + originjitter * v[2];
556 part->vel[0] = pvx + velocityjitter * v[0];
557 part->vel[1] = pvy + velocityjitter * v[1];
558 part->vel[2] = pvz + velocityjitter * v[2];
560 part->airfriction = pairfriction;
561 part->liquidfriction = pliquidfriction;
562 part->die = cl.time + lifetime;
563 part->delayedcollisions = 0;
564 part->qualityreduction = pqualityreduction;
565 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
566 if (part->typeindex == pt_rain)
570 float lifetime = part->die - cl.time;
573 // turn raindrop into simple spark and create delayedspawn splash effect
574 part->typeindex = pt_spark;
576 VectorMA(part->org, lifetime, part->vel, endvec);
577 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
578 part->die = cl.time + lifetime * trace.fraction;
579 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1);
582 part2->delayedspawn = part->die;
583 part2->die += part->die - cl.time;
584 for (i = rand() & 7;i < 10;i++)
586 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
589 part2->delayedspawn = part->die;
590 part2->die += part->die - cl.time;
595 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
597 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
600 VectorMA(part->org, lifetime, part->vel, endvec);
601 trace = CL_Move(part->org, vec3_origin, vec3_origin, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
602 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
607 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
611 if (!cl_decals.integer)
613 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
614 if (cl.free_decal >= cl.max_decals)
616 decal = &cl.decals[cl.free_decal++];
617 if (cl.num_decals < cl.free_decal)
618 cl.num_decals = cl.free_decal;
619 memset(decal, 0, sizeof(*decal));
620 decal->typeindex = pt_decal;
621 decal->texnum = texnum;
622 VectorAdd(org, normal, decal->org);
623 VectorCopy(normal, decal->normal);
625 decal->alpha = alpha;
626 decal->time2 = cl.time;
627 l2 = (int)lhrandom(0.5, 256.5);
629 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
630 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
631 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
632 decal->owner = hitent;
633 decal->clusterindex = -1000; // no vis culling unless we're sure
636 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
637 decal->ownermodel = cl.entities[decal->owner].render.model;
638 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
639 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
643 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
645 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
647 decal->clusterindex = leaf->clusterindex;
652 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
655 float bestfrac, bestorg[3], bestnormal[3];
657 int besthitent = 0, hitent;
660 for (i = 0;i < 32;i++)
663 VectorMA(org, maxdist, org2, org2);
664 trace = CL_Move(org, vec3_origin, vec3_origin, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
665 // take the closest trace result that doesn't end up hitting a NOMARKS
666 // surface (sky for example)
667 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
669 bestfrac = trace.fraction;
671 VectorCopy(trace.endpos, bestorg);
672 VectorCopy(trace.plane.normal, bestnormal);
676 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
679 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
680 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
681 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)
684 matrix4x4_t tempmatrix;
685 VectorLerp(originmins, 0.5, originmaxs, center);
686 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
687 if (effectnameindex == EFFECT_SVC_PARTICLE)
689 if (cl_particles.integer)
691 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
693 CL_ParticleExplosion(center);
694 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
695 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
698 count *= cl_particles_quality.value;
699 for (;count > 0;count--)
701 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
702 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 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, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
707 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
708 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
709 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
710 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
711 else if (effectnameindex == EFFECT_TE_SPIKE)
713 if (cl_particles_bulletimpacts.integer)
715 if (cl_particles_quake.integer)
717 if (cl_particles_smoke.integer)
718 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
722 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
723 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
724 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
728 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
729 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
731 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
733 if (cl_particles_bulletimpacts.integer)
735 if (cl_particles_quake.integer)
737 if (cl_particles_smoke.integer)
738 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
742 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
743 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
744 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
748 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
749 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
750 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);
752 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
754 if (cl_particles_bulletimpacts.integer)
756 if (cl_particles_quake.integer)
758 if (cl_particles_smoke.integer)
759 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
763 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
764 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
765 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
769 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
770 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
772 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
774 if (cl_particles_bulletimpacts.integer)
776 if (cl_particles_quake.integer)
778 if (cl_particles_smoke.integer)
779 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
783 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
784 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
785 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
789 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
790 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
791 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);
793 else if (effectnameindex == EFFECT_TE_BLOOD)
795 if (!cl_particles_blood.integer)
797 if (cl_particles_quake.integer)
798 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
801 static double bloodaccumulator = 0;
802 //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
803 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
804 for (;bloodaccumulator > 0;bloodaccumulator--)
805 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 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, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
808 else if (effectnameindex == EFFECT_TE_SPARK)
809 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
810 else if (effectnameindex == EFFECT_TE_PLASMABURN)
812 // plasma scorch mark
813 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
814 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
815 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
817 else if (effectnameindex == EFFECT_TE_GUNSHOT)
819 if (cl_particles_bulletimpacts.integer)
821 if (cl_particles_quake.integer)
822 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
825 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
826 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
827 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
831 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
832 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
834 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
836 if (cl_particles_bulletimpacts.integer)
838 if (cl_particles_quake.integer)
839 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
842 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
843 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
844 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
848 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
849 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
850 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);
852 else if (effectnameindex == EFFECT_TE_EXPLOSION)
854 CL_ParticleExplosion(center);
855 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);
857 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
859 CL_ParticleExplosion(center);
860 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);
862 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
864 if (cl_particles_quake.integer)
867 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
870 CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
872 CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
876 CL_ParticleExplosion(center);
877 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);
879 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
880 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);
881 else if (effectnameindex == EFFECT_TE_FLAMEJET)
883 count *= cl_particles_quality.value;
885 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
887 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
889 float i, j, inc, vel;
892 inc = 8 / cl_particles_quality.value;
893 for (i = -128;i < 128;i += inc)
895 for (j = -128;j < 128;j += inc)
897 dir[0] = j + lhrandom(0, inc);
898 dir[1] = i + lhrandom(0, inc);
900 org[0] = center[0] + dir[0];
901 org[1] = center[1] + dir[1];
902 org[2] = center[2] + lhrandom(0, 64);
903 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
904 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
908 else if (effectnameindex == EFFECT_TE_TELEPORT)
910 float i, j, k, inc, vel;
913 if (cl_particles_quake.integer)
914 inc = 4 / cl_particles_quality.value;
916 inc = 8 / cl_particles_quality.value;
917 for (i = -16;i < 16;i += inc)
919 for (j = -16;j < 16;j += inc)
921 for (k = -24;k < 32;k += inc)
923 VectorSet(dir, i*8, j*8, k*8);
924 VectorNormalize(dir);
925 vel = lhrandom(50, 113);
926 if (cl_particles_quake.integer)
927 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 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, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
929 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
933 if (!cl_particles_quake.integer)
934 CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
935 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);
937 else if (effectnameindex == EFFECT_TE_TEI_G3)
938 CL_NewParticle(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, false, 0, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
939 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
941 if (cl_particles_smoke.integer)
943 count *= 0.25f * cl_particles_quality.value;
945 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
948 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
950 CL_ParticleExplosion(center);
951 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);
953 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
956 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
957 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
958 if (cl_particles_smoke.integer)
959 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
960 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
961 if (cl_particles_sparks.integer)
962 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
963 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
964 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);
966 else if (effectnameindex == EFFECT_EF_FLAME)
968 count *= 300 * cl_particles_quality.value;
970 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
971 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);
973 else if (effectnameindex == EFFECT_EF_STARDUST)
975 count *= 200 * cl_particles_quality.value;
977 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
978 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);
980 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
984 int smoke, blood, bubbles, r, color;
986 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
989 Vector4Set(light, 0, 0, 0, 0);
991 if (effectnameindex == EFFECT_TR_ROCKET)
992 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
993 else if (effectnameindex == EFFECT_TR_VORESPIKE)
995 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
996 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
998 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1000 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1001 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1005 matrix4x4_t tempmatrix;
1006 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1007 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1008 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1012 if (!spawnparticles)
1015 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1018 VectorSubtract(originmaxs, originmins, dir);
1019 len = VectorNormalizeLength(dir);
1022 dec = -ent->persistent.trail_time;
1023 ent->persistent.trail_time += len;
1024 if (ent->persistent.trail_time < 0.01f)
1027 // if we skip out, leave it reset
1028 ent->persistent.trail_time = 0.0f;
1033 // advance into this frame to reach the first puff location
1034 VectorMA(originmins, dec, dir, pos);
1037 smoke = cl_particles.integer && cl_particles_smoke.integer;
1038 blood = cl_particles.integer && cl_particles_blood.integer;
1039 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1040 qd = 1.0f / cl_particles_quality.value;
1047 if (effectnameindex == EFFECT_TR_BLOOD)
1049 if (cl_particles_quake.integer)
1051 color = particlepalette[67 + (rand()&3)];
1052 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1057 CL_NewParticle(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, 1, -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, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1060 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1062 if (cl_particles_quake.integer)
1065 color = particlepalette[67 + (rand()&3)];
1066 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1071 CL_NewParticle(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, 1, -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, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1077 if (effectnameindex == EFFECT_TR_ROCKET)
1079 if (cl_particles_quake.integer)
1082 color = particlepalette[ramp3[r]];
1083 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1087 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1088 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1091 else if (effectnameindex == EFFECT_TR_GRENADE)
1093 if (cl_particles_quake.integer)
1096 color = particlepalette[ramp3[r]];
1097 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1101 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1104 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1106 if (cl_particles_quake.integer)
1109 color = particlepalette[52 + (rand()&7)];
1110 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1111 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1113 else if (gamemode == GAME_GOODVSBAD2)
1116 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1120 color = particlepalette[20 + (rand()&7)];
1121 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1124 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1126 if (cl_particles_quake.integer)
1129 color = particlepalette[230 + (rand()&7)];
1130 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1131 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1135 color = particlepalette[226 + (rand()&7)];
1136 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1139 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1141 if (cl_particles_quake.integer)
1143 color = particlepalette[152 + (rand()&3)];
1144 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1146 else if (gamemode == GAME_GOODVSBAD2)
1149 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1151 else if (gamemode == GAME_PRYDON)
1154 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1157 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1159 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1162 CL_NewParticle(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, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1164 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1167 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1169 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1170 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1174 if (effectnameindex == EFFECT_TR_ROCKET)
1175 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1176 else if (effectnameindex == EFFECT_TR_GRENADE)
1177 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1179 // advance to next time and position
1182 VectorMA (pos, dec, dir, pos);
1185 ent->persistent.trail_time = len;
1187 else if (developer.integer >= 1)
1188 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1191 // this is also called on point effects with spawndlight = true and
1192 // spawnparticles = true
1193 // it is called CL_ParticleTrail because most code does not want to supply
1194 // these parameters, only trail handling does
1195 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)
1198 qboolean found = false;
1199 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1201 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1202 return; // no such effect
1204 VectorLerp(originmins, 0.5, originmaxs, center);
1205 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1207 int effectinfoindex;
1210 particleeffectinfo_t *info;
1212 vec3_t centervelocity;
1218 qboolean underwater;
1219 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1220 VectorLerp(originmins, 0.5, originmaxs, center);
1221 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1222 supercontents = CL_PointSuperContents(center);
1223 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1224 VectorSubtract(originmaxs, originmins, traildir);
1225 traillen = VectorLength(traildir);
1226 VectorNormalize(traildir);
1227 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1229 if (info->effectnameindex == effectnameindex)
1232 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1234 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1237 // spawn a dlight if requested
1238 if (info->lightradiusstart > 0 && spawndlight)
1240 matrix4x4_t tempmatrix;
1241 if (info->trailspacing > 0)
1242 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1244 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1245 if (info->lighttime > 0 && info->lightradiusfade > 0)
1247 // light flash (explosion, etc)
1248 // called when effect starts
1249 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);
1254 // called by CL_LinkNetworkEntity
1255 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1256 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.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);
1257 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1261 if (!spawnparticles)
1266 if (info->tex[1] > info->tex[0])
1268 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1269 tex = min(tex, info->tex[1] - 1);
1271 if(info->staintex[0] < 0)
1272 staintex = info->staintex[0];
1275 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1276 staintex = min(staintex, info->staintex[1] - 1);
1278 if (info->particletype == pt_decal)
1279 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]);
1280 else if (info->orientation == PARTICLE_BEAM)
1281 CL_NewParticle(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, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1284 if (!cl_particles.integer)
1286 switch (info->particletype)
1288 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1289 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1290 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1291 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1292 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1293 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1296 VectorCopy(originmins, trailpos);
1297 if (info->trailspacing > 0)
1299 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1300 trailstep = info->trailspacing / cl_particles_quality.value;
1304 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1307 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1308 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1310 if (info->tex[1] > info->tex[0])
1312 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1313 tex = min(tex, info->tex[1] - 1);
1317 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1318 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1319 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1322 CL_NewParticle(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, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1324 VectorMA(trailpos, trailstep, traildir, trailpos);
1331 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1334 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)
1336 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1344 void CL_EntityParticles (const entity_t *ent)
1347 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1348 static vec3_t avelocities[NUMVERTEXNORMALS];
1349 if (!cl_particles.integer) return;
1350 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1352 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1354 if (!avelocities[0][0])
1355 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1356 avelocities[0][i] = lhrandom(0, 2.55);
1358 for (i = 0;i < NUMVERTEXNORMALS;i++)
1360 yaw = cl.time * avelocities[i][0];
1361 pitch = cl.time * avelocities[i][1];
1362 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1363 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1364 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1365 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1370 void CL_ReadPointFile_f (void)
1372 vec3_t org, leakorg;
1374 char *pointfile = NULL, *pointfilepos, *t, tchar;
1375 char name[MAX_OSPATH];
1380 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1381 strlcat (name, ".pts", sizeof (name));
1382 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1385 Con_Printf("Could not open %s\n", name);
1389 Con_Printf("Reading %s...\n", name);
1390 VectorClear(leakorg);
1393 pointfilepos = pointfile;
1394 while (*pointfilepos)
1396 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1401 while (*t && *t != '\n' && *t != '\r')
1405 #if _MSC_VER >= 1400
1406 #define sscanf sscanf_s
1408 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1414 VectorCopy(org, leakorg);
1417 if (cl.num_particles < cl.max_particles - 3)
1420 CL_NewParticle(pt_alphastatic, 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, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1423 Mem_Free(pointfile);
1424 VectorCopy(leakorg, org);
1425 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1427 CL_NewParticle(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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1428 CL_NewParticle(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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1429 CL_NewParticle(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, false, 1<<30, 1, PBLEND_ADD, PARTICLE_BEAM, -1, -1, -1);
1434 CL_ParseParticleEffect
1436 Parse an effect out of the server message
1439 void CL_ParseParticleEffect (void)
1442 int i, count, msgcount, color;
1444 MSG_ReadVector(org, cls.protocol);
1445 for (i=0 ; i<3 ; i++)
1446 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1447 msgcount = MSG_ReadByte ();
1448 color = MSG_ReadByte ();
1450 if (msgcount == 255)
1455 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1460 CL_ParticleExplosion
1464 void CL_ParticleExplosion (const vec3_t org)
1470 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1471 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1473 if (cl_particles_quake.integer)
1475 for (i = 0;i < 1024;i++)
1481 color = particlepalette[ramp1[r]];
1482 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1486 color = particlepalette[ramp2[r]];
1487 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1493 i = CL_PointSuperContents(org);
1494 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1496 if (cl_particles.integer && cl_particles_bubbles.integer)
1497 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1498 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1502 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1504 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1508 for (k = 0;k < 16;k++)
1511 VectorMA(org, 128, v2, v);
1512 trace = CL_Move(org, vec3_origin, vec3_origin, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1513 if (trace.fraction >= 0.1)
1516 VectorSubtract(trace.endpos, org, v2);
1517 VectorScale(v2, 2.0f, v2);
1518 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1524 if (cl_particles_explosions_shell.integer)
1525 R_NewExplosion(org);
1530 CL_ParticleExplosion2
1534 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1537 if (!cl_particles.integer) return;
1539 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1541 k = particlepalette[colorStart + (i % colorLength)];
1542 if (cl_particles_quake.integer)
1543 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1545 CL_NewParticle(pt_alphastatic, 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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1549 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1551 if (cl_particles_sparks.integer)
1553 sparkcount *= cl_particles_quality.value;
1554 while(sparkcount-- > 0)
1555 CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1559 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1561 if (cl_particles_smoke.integer)
1563 smokecount *= cl_particles_quality.value;
1564 while(smokecount-- > 0)
1565 CL_NewParticle(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, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1569 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)
1572 if (!cl_particles.integer) return;
1574 count = (int)(count * cl_particles_quality.value);
1577 k = particlepalette[colorbase + (rand()&3)];
1578 CL_NewParticle(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, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1582 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1585 float minz, maxz, lifetime = 30;
1586 if (!cl_particles.integer) return;
1587 if (dir[2] < 0) // falling
1589 minz = maxs[2] + dir[2] * 0.1;
1592 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1597 maxz = maxs[2] + dir[2] * 0.1;
1599 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1602 count = (int)(count * cl_particles_quality.value);
1607 if (!cl_particles_rain.integer) break;
1608 count *= 4; // ick, this should be in the mod or maps?
1612 k = particlepalette[colorbase + (rand()&3)];
1613 if (gamemode == GAME_GOODVSBAD2)
1614 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 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, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1616 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 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, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1620 if (!cl_particles_snow.integer) break;
1623 k = particlepalette[colorbase + (rand()&3)];
1624 if (gamemode == GAME_GOODVSBAD2)
1625 CL_NewParticle(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, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1627 CL_NewParticle(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, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1631 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1635 #define MAX_PARTICLETEXTURES 64
1636 // particletexture_t is a rectangle in the particlefonttexture
1637 typedef struct particletexture_s
1639 rtexture_t *texture;
1640 float s1, t1, s2, t2;
1644 static rtexturepool_t *particletexturepool;
1645 static rtexture_t *particlefonttexture;
1646 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
1648 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1649 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1650 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1651 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1653 #define PARTICLETEXTURESIZE 64
1654 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1656 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1660 dz = 1 - (dx*dx+dy*dy);
1661 if (dz > 0) // it does hit the sphere
1665 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1666 VectorNormalize(normal);
1667 dot = DotProduct(normal, light);
1668 if (dot > 0.5) // interior reflection
1669 f += ((dot * 2) - 1);
1670 else if (dot < -0.5) // exterior reflection
1671 f += ((dot * -2) - 1);
1673 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1674 VectorNormalize(normal);
1675 dot = DotProduct(normal, light);
1676 if (dot > 0.5) // interior reflection
1677 f += ((dot * 2) - 1);
1678 else if (dot < -0.5) // exterior reflection
1679 f += ((dot * -2) - 1);
1681 f += 16; // just to give it a haze so you can see the outline
1682 f = bound(0, f, 255);
1683 return (unsigned char) f;
1689 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1691 int basex, basey, y;
1692 basex = ((texnum >> 0) & 7) * PARTICLETEXTURESIZE;
1693 basey = ((texnum >> 3) & 7) * PARTICLETEXTURESIZE;
1694 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1695 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1698 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1701 float cx, cy, dx, dy, f, iradius;
1703 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1704 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1705 iradius = 1.0f / radius;
1706 alpha *= (1.0f / 255.0f);
1707 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1709 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1713 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1718 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1719 d[0] += (int)(f * (blue - d[0]));
1720 d[1] += (int)(f * (green - d[1]));
1721 d[2] += (int)(f * (red - d[2]));
1727 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1730 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1732 data[0] = bound(minb, data[0], maxb);
1733 data[1] = bound(ming, data[1], maxg);
1734 data[2] = bound(minr, data[2], maxr);
1738 void particletextureinvert(unsigned char *data)
1741 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1743 data[0] = 255 - data[0];
1744 data[1] = 255 - data[1];
1745 data[2] = 255 - data[2];
1749 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1750 static void R_InitBloodTextures (unsigned char *particletexturedata)
1753 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1756 for (i = 0;i < 8;i++)
1758 memset(&data[0][0][0], 255, sizeof(data));
1759 for (k = 0;k < 24;k++)
1760 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1761 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1762 particletextureinvert(&data[0][0][0]);
1763 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1767 for (i = 0;i < 8;i++)
1769 memset(&data[0][0][0], 255, sizeof(data));
1771 for (j = 1;j < 10;j++)
1772 for (k = min(j, m - 1);k < m;k++)
1773 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1774 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1775 particletextureinvert(&data[0][0][0]);
1776 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1781 //uncomment this to make engine save out particle font to a tga file when run
1782 //#define DUMPPARTICLEFONT
1784 static void R_InitParticleTexture (void)
1786 int x, y, d, i, k, m;
1790 // a note: decals need to modulate (multiply) the background color to
1791 // properly darken it (stain), and they need to be able to alpha fade,
1792 // this is a very difficult challenge because it means fading to white
1793 // (no change to background) rather than black (darkening everything
1794 // behind the whole decal polygon), and to accomplish this the texture is
1795 // inverted (dark red blood on white background becomes brilliant cyan
1796 // and white on black background) so we can alpha fade it to black, then
1797 // we invert it again during the blendfunc to make it work...
1799 #ifndef DUMPPARTICLEFONT
1800 particlefonttexture = loadtextureimage(particletexturepool, "particles/particlefont.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1801 if (!particlefonttexture)
1804 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1805 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1806 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1809 for (i = 0;i < 8;i++)
1811 memset(&data[0][0][0], 255, sizeof(data));
1814 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1816 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1817 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1819 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1821 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1822 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1824 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1825 d = (noise2[y][x] - 128) * 3 + 192;
1827 d = (int)(d * (1-(dx*dx+dy*dy)));
1828 d = (d * noise1[y][x]) >> 7;
1829 d = bound(0, d, 255);
1830 data[y][x][3] = (unsigned char) d;
1837 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1841 memset(&data[0][0][0], 255, sizeof(data));
1842 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1844 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1845 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1847 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1848 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1849 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1852 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1855 memset(&data[0][0][0], 255, sizeof(data));
1856 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1858 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1859 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1861 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1862 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1863 d = bound(0, d, 255);
1864 data[y][x][3] = (unsigned char) d;
1867 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1870 memset(&data[0][0][0], 255, sizeof(data));
1871 light[0] = 1;light[1] = 1;light[2] = 1;
1872 VectorNormalize(light);
1873 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1875 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1876 // stretch upper half of bubble by +50% and shrink lower half by -50%
1877 // (this gives an elongated teardrop shape)
1879 dy = (dy - 0.5f) * 2.0f;
1881 dy = (dy - 0.5f) / 1.5f;
1882 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1884 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1885 // shrink bubble width to half
1887 data[y][x][3] = shadebubble(dx, dy, light);
1890 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1893 memset(&data[0][0][0], 255, sizeof(data));
1894 light[0] = 1;light[1] = 1;light[2] = 1;
1895 VectorNormalize(light);
1896 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1898 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1899 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1901 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1902 data[y][x][3] = shadebubble(dx, dy, light);
1905 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1907 // Blood particles and blood decals
1908 R_InitBloodTextures (particletexturedata);
1911 for (i = 0;i < 8;i++)
1913 memset(&data[0][0][0], 255, sizeof(data));
1914 for (k = 0;k < 12;k++)
1915 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1916 for (k = 0;k < 3;k++)
1917 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1918 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1919 particletextureinvert(&data[0][0][0]);
1920 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1923 #ifdef DUMPPARTICLEFONT
1924 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1927 particlefonttexture = R_LoadTexture2D(particletexturepool, "particlefont", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1929 Mem_Free(particletexturedata);
1931 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1933 int basex = ((i >> 0) & 7) * PARTICLETEXTURESIZE;
1934 int basey = ((i >> 3) & 7) * PARTICLETEXTURESIZE;
1935 particletexture[i].texture = particlefonttexture;
1936 particletexture[i].s1 = (basex + 1) / (float)PARTICLEFONTSIZE;
1937 particletexture[i].t1 = (basey + 1) / (float)PARTICLEFONTSIZE;
1938 particletexture[i].s2 = (basex + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1939 particletexture[i].t2 = (basey + PARTICLETEXTURESIZE - 1) / (float)PARTICLEFONTSIZE;
1942 #ifndef DUMPPARTICLEFONT
1943 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
1944 if (!particletexture[tex_beam].texture)
1947 unsigned char noise3[64][64], data2[64][16][4];
1949 fractalnoise(&noise3[0][0], 64, 4);
1951 for (y = 0;y < 64;y++)
1953 dy = (y - 0.5f*64) / (64*0.5f-1);
1954 for (x = 0;x < 16;x++)
1956 dx = (x - 0.5f*16) / (16*0.5f-2);
1957 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
1958 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
1959 data2[y][x][3] = 255;
1963 #ifdef DUMPPARTICLEFONT
1964 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
1966 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
1968 particletexture[tex_beam].s1 = 0;
1969 particletexture[tex_beam].t1 = 0;
1970 particletexture[tex_beam].s2 = 1;
1971 particletexture[tex_beam].t2 = 1;
1974 static void r_part_start(void)
1977 // generate particlepalette for convenience from the main one
1978 for (i = 0;i < 256;i++)
1979 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
1980 particletexturepool = R_AllocTexturePool();
1981 R_InitParticleTexture ();
1982 CL_Particles_LoadEffectInfo();
1985 static void r_part_shutdown(void)
1987 R_FreeTexturePool(&particletexturepool);
1990 static void r_part_newmap(void)
1992 CL_Particles_LoadEffectInfo();
1995 #define BATCHSIZE 256
1996 unsigned short particle_elements[BATCHSIZE*6];
1998 void R_Particles_Init (void)
2001 for (i = 0;i < BATCHSIZE;i++)
2003 particle_elements[i*6+0] = i*4+0;
2004 particle_elements[i*6+1] = i*4+1;
2005 particle_elements[i*6+2] = i*4+2;
2006 particle_elements[i*6+3] = i*4+0;
2007 particle_elements[i*6+4] = i*4+2;
2008 particle_elements[i*6+5] = i*4+3;
2011 Cvar_RegisterVariable(&r_drawparticles);
2012 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2013 Cvar_RegisterVariable(&r_drawdecals);
2014 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2015 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2018 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2020 int surfacelistindex;
2022 float *v3f, *t2f, *c4f;
2023 particletexture_t *tex;
2024 float right[3], up[3], size, ca;
2025 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2026 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2028 r_refdef.stats.decals += numsurfaces;
2029 R_Mesh_Matrix(&identitymatrix);
2030 R_Mesh_ResetTextureState();
2031 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2032 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2033 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2034 R_SetupGenericShader(true);
2035 GL_DepthMask(false);
2036 GL_DepthRange(0, 1);
2037 GL_PolygonOffset(0, 0);
2039 GL_CullFace(GL_NONE);
2041 // generate all the vertices at once
2042 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2044 d = cl.decals + surfacelist[surfacelistindex];
2047 c4f = particle_color4f + 16*surfacelistindex;
2048 ca = d->alpha * alphascale;
2049 if (r_refdef.fogenabled)
2050 ca *= FogPoint_World(d->org);
2051 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2052 Vector4Copy(c4f, c4f + 4);
2053 Vector4Copy(c4f, c4f + 8);
2054 Vector4Copy(c4f, c4f + 12);
2056 // calculate vertex positions
2057 size = d->size * cl_particles_size.value;
2058 VectorVectors(d->normal, right, up);
2059 VectorScale(right, size, right);
2060 VectorScale(up, size, up);
2061 v3f = particle_vertex3f + 12*surfacelistindex;
2062 v3f[ 0] = d->org[0] - right[0] - up[0];
2063 v3f[ 1] = d->org[1] - right[1] - up[1];
2064 v3f[ 2] = d->org[2] - right[2] - up[2];
2065 v3f[ 3] = d->org[0] - right[0] + up[0];
2066 v3f[ 4] = d->org[1] - right[1] + up[1];
2067 v3f[ 5] = d->org[2] - right[2] + up[2];
2068 v3f[ 6] = d->org[0] + right[0] + up[0];
2069 v3f[ 7] = d->org[1] + right[1] + up[1];
2070 v3f[ 8] = d->org[2] + right[2] + up[2];
2071 v3f[ 9] = d->org[0] + right[0] - up[0];
2072 v3f[10] = d->org[1] + right[1] - up[1];
2073 v3f[11] = d->org[2] + right[2] - up[2];
2075 // calculate texcoords
2076 tex = &particletexture[d->texnum];
2077 t2f = particle_texcoord2f + 8*surfacelistindex;
2078 t2f[0] = tex->s1;t2f[1] = tex->t2;
2079 t2f[2] = tex->s1;t2f[3] = tex->t1;
2080 t2f[4] = tex->s2;t2f[5] = tex->t1;
2081 t2f[6] = tex->s2;t2f[7] = tex->t2;
2084 // now render the decals all at once
2085 // (this assumes they all use one particle font texture!)
2086 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2087 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2088 GL_LockArrays(0, numsurfaces*4);
2089 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2090 GL_LockArrays(0, 0);
2093 void R_DrawDecals (void)
2101 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2102 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2104 // LordHavoc: early out conditions
2105 if ((!cl.num_decals) || (!r_drawdecals.integer))
2108 decalfade = frametime * 256 / cl_decals_fadetime.value;
2109 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2110 drawdist2 = drawdist2*drawdist2;
2112 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2114 if (!decal->typeindex)
2117 if (cl.time > decal->time2 + cl_decals_time.value)
2119 decal->alpha -= decalfade;
2120 if (decal->alpha <= 0)
2126 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2128 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2129 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2135 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2138 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2139 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2142 decal->typeindex = 0;
2143 if (cl.free_decal > i)
2147 // reduce cl.num_decals if possible
2148 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2151 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2153 decal_t *olddecals = cl.decals;
2154 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2155 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2156 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2157 Mem_Free(olddecals);
2161 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2163 int surfacelistindex;
2164 int batchstart, batchcount;
2165 const particle_t *p;
2167 rtexture_t *texture;
2168 float *v3f, *t2f, *c4f;
2169 particletexture_t *tex;
2170 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2171 float ambient[3], diffuse[3], diffusenormal[3];
2172 vec4_t colormultiplier;
2173 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2175 Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f));
2177 r_refdef.stats.particles += numsurfaces;
2178 R_Mesh_Matrix(&identitymatrix);
2179 R_Mesh_ResetTextureState();
2180 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2181 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2182 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2183 R_SetupGenericShader(true);
2184 GL_DepthMask(false);
2185 GL_DepthRange(0, 1);
2186 GL_PolygonOffset(0, 0);
2188 GL_CullFace(GL_NONE);
2190 // first generate all the vertices at once
2191 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2193 p = cl.particles + surfacelist[surfacelistindex];
2195 blendmode = p->blendmode;
2197 c4f[0] = p->color[0] * colormultiplier[0];
2198 c4f[1] = p->color[1] * colormultiplier[1];
2199 c4f[2] = p->color[2] * colormultiplier[2];
2200 c4f[3] = p->alpha * colormultiplier[3];
2203 case PBLEND_INVALID:
2206 // additive and modulate can just fade out in fog (this is correct)
2207 if (r_refdef.fogenabled)
2208 c4f[3] *= FogPoint_World(p->org);
2209 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2216 // note: lighting is not cheap!
2217 if (particletype[p->typeindex].lighting)
2219 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2220 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2221 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2222 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2224 // mix in the fog color
2225 if (r_refdef.fogenabled)
2227 fog = FogPoint_World(p->org);
2229 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2230 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2231 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2235 // copy the color into the other three vertices
2236 Vector4Copy(c4f, c4f + 4);
2237 Vector4Copy(c4f, c4f + 8);
2238 Vector4Copy(c4f, c4f + 12);
2240 size = p->size * cl_particles_size.value;
2241 tex = &particletexture[p->texnum];
2242 switch(p->orientation)
2244 case PARTICLE_INVALID:
2245 case PARTICLE_BILLBOARD:
2246 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2247 VectorScale(r_refdef.view.up, size, up);
2248 v3f[ 0] = p->org[0] - right[0] - up[0];
2249 v3f[ 1] = p->org[1] - right[1] - up[1];
2250 v3f[ 2] = p->org[2] - right[2] - up[2];
2251 v3f[ 3] = p->org[0] - right[0] + up[0];
2252 v3f[ 4] = p->org[1] - right[1] + up[1];
2253 v3f[ 5] = p->org[2] - right[2] + up[2];
2254 v3f[ 6] = p->org[0] + right[0] + up[0];
2255 v3f[ 7] = p->org[1] + right[1] + up[1];
2256 v3f[ 8] = p->org[2] + right[2] + up[2];
2257 v3f[ 9] = p->org[0] + right[0] - up[0];
2258 v3f[10] = p->org[1] + right[1] - up[1];
2259 v3f[11] = p->org[2] + right[2] - up[2];
2260 t2f[0] = tex->s1;t2f[1] = tex->t2;
2261 t2f[2] = tex->s1;t2f[3] = tex->t1;
2262 t2f[4] = tex->s2;t2f[5] = tex->t1;
2263 t2f[6] = tex->s2;t2f[7] = tex->t2;
2265 case PARTICLE_ORIENTED_DOUBLESIDED:
2266 VectorVectors(p->vel, right, up);
2267 VectorScale(right, size * p->stretch, right);
2268 VectorScale(up, size, up);
2269 v3f[ 0] = p->org[0] - right[0] - up[0];
2270 v3f[ 1] = p->org[1] - right[1] - up[1];
2271 v3f[ 2] = p->org[2] - right[2] - up[2];
2272 v3f[ 3] = p->org[0] - right[0] + up[0];
2273 v3f[ 4] = p->org[1] - right[1] + up[1];
2274 v3f[ 5] = p->org[2] - right[2] + up[2];
2275 v3f[ 6] = p->org[0] + right[0] + up[0];
2276 v3f[ 7] = p->org[1] + right[1] + up[1];
2277 v3f[ 8] = p->org[2] + right[2] + up[2];
2278 v3f[ 9] = p->org[0] + right[0] - up[0];
2279 v3f[10] = p->org[1] + right[1] - up[1];
2280 v3f[11] = p->org[2] + right[2] - up[2];
2281 t2f[0] = tex->s1;t2f[1] = tex->t2;
2282 t2f[2] = tex->s1;t2f[3] = tex->t1;
2283 t2f[4] = tex->s2;t2f[5] = tex->t1;
2284 t2f[6] = tex->s2;t2f[7] = tex->t2;
2286 case PARTICLE_SPARK:
2287 len = VectorLength(p->vel);
2288 VectorNormalize2(p->vel, up);
2289 lenfactor = p->stretch * 0.04 * len;
2290 if(lenfactor < size * 0.5)
2291 lenfactor = size * 0.5;
2292 VectorMA(p->org, -lenfactor, up, v);
2293 VectorMA(p->org, lenfactor, up, up2);
2294 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2295 t2f[0] = tex->s1;t2f[1] = tex->t2;
2296 t2f[2] = tex->s1;t2f[3] = tex->t1;
2297 t2f[4] = tex->s2;t2f[5] = tex->t1;
2298 t2f[6] = tex->s2;t2f[7] = tex->t2;
2301 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2302 VectorSubtract(p->vel, p->org, up);
2303 VectorNormalize(up);
2304 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2305 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2306 t2f[0] = 1;t2f[1] = v[0];
2307 t2f[2] = 0;t2f[3] = v[0];
2308 t2f[4] = 0;t2f[5] = v[1];
2309 t2f[6] = 1;t2f[7] = v[1];
2314 // now render batches of particles based on blendmode and texture
2315 blendmode = PBLEND_INVALID;
2317 GL_LockArrays(0, numsurfaces*4);
2320 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2322 p = cl.particles + surfacelist[surfacelistindex];
2324 if (blendmode != p->blendmode)
2326 blendmode = p->blendmode;
2330 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2332 case PBLEND_INVALID:
2334 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2337 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2341 if (texture != particletexture[p->texnum].texture)
2343 texture = particletexture[p->texnum].texture;
2344 R_Mesh_TexBind(0, R_GetTexture(texture));
2347 // iterate until we find a change in settings
2348 batchstart = surfacelistindex++;
2349 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2351 p = cl.particles + surfacelist[surfacelistindex];
2352 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2356 batchcount = surfacelistindex - batchstart;
2357 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2359 GL_LockArrays(0, 0);
2362 void R_DrawParticles (void)
2365 float minparticledist;
2367 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2373 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2374 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2376 // LordHavoc: early out conditions
2377 if ((!cl.num_particles) || (!r_drawparticles.integer))
2380 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2381 gravity = frametime * cl.movevars_gravity;
2382 dvel = 1+4*frametime;
2383 decalfade = frametime * 255 / cl_decals_fadetime.value;
2384 update = frametime > 0;
2385 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2386 drawdist2 = drawdist2*drawdist2;
2388 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2392 if (cl.free_particle > i)
2393 cl.free_particle = i;
2399 if (p->delayedspawn > cl.time)
2401 p->delayedspawn = 0;
2405 p->size += p->sizeincrease * frametime;
2406 p->alpha -= p->alphafade * frametime;
2408 if (p->alpha <= 0 || p->die <= cl.time)
2411 if (p->orientation != PARTICLE_BEAM && frametime > 0)
2413 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2415 if (p->typeindex == pt_blood)
2416 p->size += frametime * 8;
2418 p->vel[2] -= p->gravity * gravity;
2419 f = 1.0f - min(p->liquidfriction * frametime, 1);
2420 VectorScale(p->vel, f, p->vel);
2424 p->vel[2] -= p->gravity * gravity;
2427 f = 1.0f - min(p->airfriction * frametime, 1);
2428 VectorScale(p->vel, f, p->vel);
2432 VectorCopy(p->org, oldorg);
2433 VectorMA(p->org, frametime, p->vel, p->org);
2434 if (p->bounce && cl.time >= p->delayedcollisions)
2436 trace = CL_Move(oldorg, vec3_origin, vec3_origin, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2437 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2438 // or if the trace hit something flagged as NOIMPACT
2439 // then remove the particle
2440 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2442 VectorCopy(trace.endpos, p->org);
2443 // react if the particle hit something
2444 if (trace.fraction < 1)
2446 VectorCopy(trace.endpos, p->org);
2448 if (p->staintexnum >= 0 || p->staincolor >= 0)
2450 // blood - splash on solid
2451 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2453 if(p->staincolor >= 0)
2456 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2457 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2459 if (cl_decals.integer && p->staintexnum >= 0)
2461 // create a decal for the blood splat
2462 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->staincolor, p->staincolor, p->staintexnum, p->size * 2, p->alpha);
2467 if (p->typeindex == pt_blood)
2469 // blood - splash on solid
2470 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2472 if(p->staintexnum == -1 && p->staincolor < 0) // staintex < -1 means no stains at all
2474 R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2475 if (cl_decals.integer)
2477 // create a decal for the blood splat
2478 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
2483 else if (p->bounce < 0)
2485 // bounce -1 means remove on impact
2490 // anything else - bounce off solid
2491 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2492 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2493 if (DotProduct(p->vel, p->vel) < 0.03)
2494 VectorClear(p->vel);
2500 if (p->typeindex != pt_static)
2502 switch (p->typeindex)
2504 case pt_entityparticle:
2505 // particle that removes itself after one rendered frame
2512 a = CL_PointSuperContents(p->org);
2513 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2517 a = CL_PointSuperContents(p->org);
2518 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2522 a = CL_PointSuperContents(p->org);
2523 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2527 if (cl.time > p->time2)
2530 p->time2 = cl.time + (rand() & 3) * 0.1;
2531 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2532 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2534 a = CL_PointSuperContents(p->org);
2535 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2543 else if (p->delayedspawn)
2546 // don't render particles too close to the view (they chew fillrate)
2547 // also don't render particles behind the view (useless)
2548 // further checks to cull to the frustum would be too slow here
2549 switch(p->typeindex)
2552 // beams have no culling
2553 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2556 if(cl_particles_visculling.integer)
2557 if (!r_refdef.viewcache.world_novis)
2558 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2560 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2562 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2565 // anything else just has to be in front of the viewer and visible at this distance
2566 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2567 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2574 if (cl.free_particle > i)
2575 cl.free_particle = i;
2578 // reduce cl.num_particles if possible
2579 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2582 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2584 particle_t *oldparticles = cl.particles;
2585 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2586 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2587 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2588 Mem_Free(oldparticles);