2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
47 #define PARTICLEEFFECT_FORCENEAREST 4
48 #define PARTICLEEFFECT_DEFINED 2147483648U
50 typedef struct particleeffectinfo_s
52 int effectnameindex; // which effect this belongs to
53 // PARTICLEEFFECT_* bits
55 // blood effects may spawn very few particles, so proper fraction-overflow
56 // handling is very important, this variable keeps track of the fraction
57 double particleaccumulator;
58 // the math is: countabsolute + requestedcount * countmultiplier * quality
59 // absolute number of particles to spawn, often used for decals
60 // (unaffected by quality and requestedcount)
62 // multiplier for the number of particles CL_ParticleEffect was told to
63 // spawn, most effects do not really have a count and hence use 1, so
64 // this is often the actual count to spawn, not merely a multiplier
65 float countmultiplier;
66 // if > 0 this causes the particle to spawn in an evenly spaced line from
67 // originmins to originmaxs (causing them to describe a trail, not a box)
69 // type of particle to spawn (defines some aspects of behavior)
71 // blending mode used on this particle type
73 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
74 porientation_t orientation;
75 // range of colors to choose from in hex RRGGBB (like HTML color tags),
76 // randomly interpolated at spawn
77 unsigned int color[2];
78 // a random texture is chosen in this range (note the second value is one
79 // past the last choosable, so for example 8,16 chooses any from 8 up and
81 // if start and end of the range are the same, no randomization is done
83 // range of size values randomly chosen when spawning, plus size increase over time
85 // range of alpha values randomly chosen when spawning, plus alpha fade
87 // how long the particle should live (note it is also removed if alpha drops to 0)
89 // how much gravity affects this particle (negative makes it fly up!)
91 // how much bounce the particle has when it hits a surface
92 // if negative the particle is removed on impact
94 // if in air this friction is applied
95 // if negative the particle accelerates
97 // if in liquid (water/slime/lava) this friction is applied
98 // if negative the particle accelerates
100 // these offsets are added to the values given to particleeffect(), and
101 // then an ellipsoid-shaped jitter is added as defined by these
102 // (they are the 3 radii)
104 // stretch velocity factor (used for sparks)
105 float originoffset[3];
106 float relativeoriginoffset[3];
107 float velocityoffset[3];
108 float relativevelocityoffset[3];
109 float originjitter[3];
110 float velocityjitter[3];
111 float velocitymultiplier;
112 // an effect can also spawn a dlight
113 float lightradiusstart;
114 float lightradiusfade;
117 qboolean lightshadow;
119 float lightcorona[2];
120 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
125 float rotate[4]; // min/max base angle, min/max rotation over time
127 particleeffectinfo_t;
129 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
131 int numparticleeffectinfo;
132 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
134 static int particlepalette[256];
136 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
137 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
138 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
139 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
140 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
141 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
142 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
143 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
144 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
145 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
146 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
147 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
148 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
149 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
150 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
151 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
152 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
153 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
154 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
155 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
156 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
157 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
158 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
159 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
160 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
161 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
162 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
163 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
164 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
165 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
166 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
167 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
170 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
171 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
172 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
174 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
176 // particletexture_t is a rectangle in the particlefonttexture
177 typedef struct particletexture_s
180 float s1, t1, s2, t2;
184 static rtexturepool_t *particletexturepool;
185 static rtexture_t *particlefonttexture;
186 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
187 skinframe_t *decalskinframe;
189 // texture numbers in particle font
190 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
191 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
192 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
193 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
194 static const int tex_rainsplash = 32;
195 static const int tex_particle = 63;
196 static const int tex_bubble = 62;
197 static const int tex_raindrop = 61;
198 static const int tex_beam = 60;
200 particleeffectinfo_t baselineparticleeffectinfo =
202 0, //int effectnameindex; // which effect this belongs to
203 // PARTICLEEFFECT_* bits
205 // blood effects may spawn very few particles, so proper fraction-overflow
206 // handling is very important, this variable keeps track of the fraction
207 0.0, //double particleaccumulator;
208 // the math is: countabsolute + requestedcount * countmultiplier * quality
209 // absolute number of particles to spawn, often used for decals
210 // (unaffected by quality and requestedcount)
211 0.0f, //float countabsolute;
212 // multiplier for the number of particles CL_ParticleEffect was told to
213 // spawn, most effects do not really have a count and hence use 1, so
214 // this is often the actual count to spawn, not merely a multiplier
215 0.0f, //float countmultiplier;
216 // if > 0 this causes the particle to spawn in an evenly spaced line from
217 // originmins to originmaxs (causing them to describe a trail, not a box)
218 0.0f, //float trailspacing;
219 // type of particle to spawn (defines some aspects of behavior)
220 pt_alphastatic, //ptype_t particletype;
221 // blending mode used on this particle type
222 PBLEND_ALPHA, //pblend_t blendmode;
223 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
224 PARTICLE_BILLBOARD, //porientation_t orientation;
225 // range of colors to choose from in hex RRGGBB (like HTML color tags),
226 // randomly interpolated at spawn
227 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
228 // a random texture is chosen in this range (note the second value is one
229 // past the last choosable, so for example 8,16 chooses any from 8 up and
231 // if start and end of the range are the same, no randomization is done
232 {63, 63 /* tex_particle */}, //int tex[2];
233 // range of size values randomly chosen when spawning, plus size increase over time
234 {1, 1, 0.0f}, //float size[3];
235 // range of alpha values randomly chosen when spawning, plus alpha fade
236 {0.0f, 256.0f, 256.0f}, //float alpha[3];
237 // how long the particle should live (note it is also removed if alpha drops to 0)
238 {16777216.0f, 16777216.0f}, //float time[2];
239 // how much gravity affects this particle (negative makes it fly up!)
240 0.0f, //float gravity;
241 // how much bounce the particle has when it hits a surface
242 // if negative the particle is removed on impact
243 0.0f, //float bounce;
244 // if in air this friction is applied
245 // if negative the particle accelerates
246 0.0f, //float airfriction;
247 // if in liquid (water/slime/lava) this friction is applied
248 // if negative the particle accelerates
249 0.0f, //float liquidfriction;
250 // these offsets are added to the values given to particleeffect(), and
251 // then an ellipsoid-shaped jitter is added as defined by these
252 // (they are the 3 radii)
253 1.0f, //float stretchfactor;
254 // stretch velocity factor (used for sparks)
255 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
258 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
259 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
260 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
261 0.0f, //float velocitymultiplier;
262 // an effect can also spawn a dlight
263 0.0f, //float lightradiusstart;
264 0.0f, //float lightradiusfade;
265 16777216.0f, //float lighttime;
266 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
267 true, //qboolean lightshadow;
268 0, //int lightcubemapnum;
269 {1.0f, 0.25f}, //float lightcorona[2];
270 {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
271 {-1, -1}, //int staintex[2];
272 {1.0f, 1.0f}, //float stainalpha[2];
273 {2.0f, 2.0f}, //float stainsize[2];
275 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
278 cvar_t cl_particles = {CVAR_CLIENT | CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
279 cvar_t cl_particles_quality = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
280 cvar_t cl_particles_alpha = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
281 cvar_t cl_particles_size = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
282 cvar_t cl_particles_quake = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
283 cvar_t cl_particles_blood = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
284 cvar_t cl_particles_blood_alpha = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
285 cvar_t cl_particles_blood_decal_alpha = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
286 cvar_t cl_particles_blood_decal_scalemin = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
287 cvar_t cl_particles_blood_decal_scalemax = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
288 cvar_t cl_particles_blood_bloodhack = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
289 cvar_t cl_particles_bulletimpacts = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
290 cvar_t cl_particles_explosions_sparks = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
291 cvar_t cl_particles_explosions_shell = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
292 cvar_t cl_particles_rain = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
293 cvar_t cl_particles_snow = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
294 cvar_t cl_particles_smoke = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
295 cvar_t cl_particles_smoke_alpha = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
296 cvar_t cl_particles_smoke_alphafade = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
297 cvar_t cl_particles_sparks = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
298 cvar_t cl_particles_bubbles = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
299 cvar_t cl_particles_visculling = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
300 cvar_t cl_particles_collisions = {CVAR_CLIENT | CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
301 cvar_t cl_particles_forcetraileffects = {CVAR_CLIENT, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"};
302 cvar_t cl_decals = {CVAR_CLIENT | CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
303 cvar_t cl_decals_time = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
304 cvar_t cl_decals_fadetime = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
305 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
306 cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
307 cvar_t cl_decals_newsystem_bloodsmears = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
308 cvar_t cl_decals_models = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models"};
309 cvar_t cl_decals_bias = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
310 cvar_t cl_decals_max = {CVAR_CLIENT | CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
313 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
319 particleeffectinfo_t *info = NULL;
320 const char *text = textstart;
322 for (linenumber = 1;;linenumber++)
325 for (arrayindex = 0;arrayindex < 16;arrayindex++)
326 argv[arrayindex][0] = 0;
329 if (!COM_ParseToken_Simple(&text, true, false, true))
331 if (!strcmp(com_token, "\n"))
335 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
341 #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;}
342 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
343 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
344 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
345 #define readfloat(var) checkparms(2);var = atof(argv[1])
346 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
347 if (!strcmp(argv[0], "effect"))
351 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
353 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
356 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
358 if (particleeffectname[effectnameindex][0])
360 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
365 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
369 // if we run out of names, abort
370 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
372 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
375 for(i = 0; i < numparticleeffectinfo; ++i)
377 info = particleeffectinfo + i;
378 if(!(info->flags & PARTICLEEFFECT_DEFINED))
379 if(info->effectnameindex == effectnameindex)
382 if(i < numparticleeffectinfo)
384 info = particleeffectinfo + numparticleeffectinfo++;
385 // copy entire info from baseline, then fix up the nameindex
386 *info = baselineparticleeffectinfo;
387 info->effectnameindex = effectnameindex;
390 else if (info == NULL)
392 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
396 info->flags |= PARTICLEEFFECT_DEFINED;
397 if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
398 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
399 else if (!strcmp(argv[0], "type"))
402 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
403 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
404 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
405 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
406 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
407 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
408 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
409 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
410 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
411 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
412 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
413 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
414 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
415 info->blendmode = particletype[info->particletype].blendmode;
416 info->orientation = particletype[info->particletype].orientation;
418 else if (!strcmp(argv[0], "blend"))
421 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
422 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
423 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
424 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
426 else if (!strcmp(argv[0], "orientation"))
429 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
430 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
431 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
432 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
433 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
435 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
436 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
437 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
438 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
439 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
440 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
441 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
442 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
443 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
444 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
445 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
446 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
447 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
448 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
449 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
450 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
451 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
452 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
453 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
454 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
455 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
456 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
457 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
458 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
459 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
460 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
461 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
462 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
463 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
464 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
465 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
466 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
467 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
468 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
469 else if (!strcmp(argv[0], "forcenearest")) {checkparms(1);info->flags |= PARTICLEEFFECT_FORCENEAREST;}
471 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
480 int CL_ParticleEffectIndexForName(const char *name)
483 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
484 if (!strcmp(particleeffectname[i], name))
489 const char *CL_ParticleEffectNameForIndex(int i)
491 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
493 return particleeffectname[i];
496 // MUST match effectnameindex_t in client.h
497 static const char *standardeffectnames[EFFECT_TOTAL] =
521 "TE_TEI_BIGEXPLOSION",
537 static void CL_Particles_LoadEffectInfo(const char *customfile)
541 unsigned char *filedata;
542 fs_offset_t filesize;
543 char filename[MAX_QPATH];
544 numparticleeffectinfo = 0;
545 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
546 memset(particleeffectname, 0, sizeof(particleeffectname));
547 for (i = 0;i < EFFECT_TOTAL;i++)
548 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
549 for (filepass = 0;;filepass++)
554 strlcpy(filename, customfile, sizeof(filename));
556 strlcpy(filename, "effectinfo.txt", sizeof(filename));
558 else if (filepass == 1)
560 if (!cl.worldbasename[0] || customfile)
562 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
566 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
569 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
574 static void CL_Particles_LoadEffectInfo_f(cmd_state_t *cmd)
576 CL_Particles_LoadEffectInfo(Cmd_Argc(cmd) > 1 ? Cmd_Argv(cmd, 1) : NULL);
584 void CL_ReadPointFile_f(cmd_state_t *cmd);
585 void CL_Particles_Init (void)
587 Cmd_AddCommand(CMD_CLIENT, "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)");
588 Cmd_AddCommand(CMD_CLIENT, "cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)");
590 Cvar_RegisterVariable (&cl_particles);
591 Cvar_RegisterVariable (&cl_particles_quality);
592 Cvar_RegisterVariable (&cl_particles_alpha);
593 Cvar_RegisterVariable (&cl_particles_size);
594 Cvar_RegisterVariable (&cl_particles_quake);
595 Cvar_RegisterVariable (&cl_particles_blood);
596 Cvar_RegisterVariable (&cl_particles_blood_alpha);
597 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
598 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
599 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
600 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
601 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
602 Cvar_RegisterVariable (&cl_particles_explosions_shell);
603 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
604 Cvar_RegisterVariable (&cl_particles_rain);
605 Cvar_RegisterVariable (&cl_particles_snow);
606 Cvar_RegisterVariable (&cl_particles_smoke);
607 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
608 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
609 Cvar_RegisterVariable (&cl_particles_sparks);
610 Cvar_RegisterVariable (&cl_particles_bubbles);
611 Cvar_RegisterVariable (&cl_particles_visculling);
612 Cvar_RegisterVariable (&cl_particles_collisions);
613 Cvar_RegisterVariable (&cl_particles_forcetraileffects);
614 Cvar_RegisterVariable (&cl_decals);
615 Cvar_RegisterVariable (&cl_decals_time);
616 Cvar_RegisterVariable (&cl_decals_fadetime);
617 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
618 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
619 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
620 Cvar_RegisterVariable (&cl_decals_models);
621 Cvar_RegisterVariable (&cl_decals_bias);
622 Cvar_RegisterVariable (&cl_decals_max);
625 void CL_Particles_Shutdown (void)
629 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
630 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
632 // list of all 26 parameters:
633 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
634 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
635 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
636 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
637 // palpha - opacity of particle as 0-255 (can be more than 255)
638 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
639 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
640 // pgravity - how much effect gravity has on the particle (0-1)
641 // 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
642 // px,py,pz - starting origin of particle
643 // pvx,pvy,pvz - starting velocity of particle
644 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
645 // blendmode - one of the PBLEND_ values
646 // orientation - one of the PARTICLE_ values
647 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
648 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
649 // stainalpha: opacity of the stain as factor for alpha
650 // stainsize: size of the stain as factor for palpha
651 // angle: base rotation of the particle geometry around its center normal
652 // spin: rotation speed of the particle geometry around its center normal
653 particle_t *CL_NewParticle(const vec3_t sortorigin, 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, float stainalpha, float stainsize, float angle, float spin, float tint[4])
658 if (!cl_particles.integer)
660 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
661 if (cl.free_particle >= cl.max_particles)
664 lifetime = palpha / min(1, palphafade);
665 part = &cl.particles[cl.free_particle++];
666 if (cl.num_particles < cl.free_particle)
667 cl.num_particles = cl.free_particle;
668 memset(part, 0, sizeof(*part));
669 VectorCopy(sortorigin, part->sortorigin);
670 part->typeindex = ptypeindex;
671 part->blendmode = blendmode;
672 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
674 particletexture_t *tex = &particletexture[ptex];
675 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
676 part->orientation = PARTICLE_VBEAM;
678 part->orientation = PARTICLE_HBEAM;
681 part->orientation = orientation;
682 l2 = (int)lhrandom(0.5, 256.5);
684 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
685 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
686 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
689 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
690 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
691 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
693 part->alpha = palpha;
694 part->alphafade = palphafade;
695 part->staintexnum = staintex;
696 if(staincolor1 >= 0 && staincolor2 >= 0)
698 l2 = (int)lhrandom(0.5, 256.5);
700 if(blendmode == PBLEND_INVMOD)
702 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
703 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
704 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
708 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
709 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
710 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
712 if(r > 0xFF) r = 0xFF;
713 if(g > 0xFF) g = 0xFF;
714 if(b > 0xFF) b = 0xFF;
718 r = part->color[0]; // -1 is shorthand for stain = particle color
722 part->staincolor[0] = r;
723 part->staincolor[1] = g;
724 part->staincolor[2] = b;
725 part->stainalpha = palpha * stainalpha;
726 part->stainsize = psize * stainsize;
729 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
731 part->color[0] *= tint[0];
732 part->color[1] *= tint[1];
733 part->color[2] *= tint[2];
735 part->alpha *= tint[3];
736 part->alphafade *= tint[3];
737 part->stainalpha *= tint[3];
741 part->sizeincrease = psizeincrease;
742 part->gravity = pgravity;
743 part->bounce = pbounce;
744 part->stretch = stretch;
746 part->org[0] = px + originjitter * v[0];
747 part->org[1] = py + originjitter * v[1];
748 part->org[2] = pz + originjitter * v[2];
749 part->vel[0] = pvx + velocityjitter * v[0];
750 part->vel[1] = pvy + velocityjitter * v[1];
751 part->vel[2] = pvz + velocityjitter * v[2];
753 part->airfriction = pairfriction;
754 part->liquidfriction = pliquidfriction;
755 part->die = cl.time + lifetime;
756 part->delayedspawn = cl.time;
757 // part->delayedcollisions = 0;
758 part->qualityreduction = pqualityreduction;
761 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
762 if (part->typeindex == pt_rain)
768 // turn raindrop into simple spark and create delayedspawn splash effect
769 part->typeindex = pt_spark;
771 VectorMA(part->org, lifetime, part->vel, endvec);
772 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
773 part->die = cl.time + lifetime * trace.fraction;
774 part2 = CL_NewParticle(endvec, 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, 1, 1, 0, 0, NULL);
777 part2->delayedspawn = part->die;
778 part2->die += part->die - cl.time;
779 for (i = rand() & 7;i < 10;i++)
781 part2 = CL_NewParticle(endvec, 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, 1, 1, 0, 0, NULL);
784 part2->delayedspawn = part->die;
785 part2->die += part->die - cl.time;
791 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
793 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
796 VectorMA(part->org, lifetime, part->vel, endvec);
797 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
798 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
805 static void CL_ImmediateBloodStain(particle_t *part)
810 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
811 if (part->staintexnum >= 0 && cl_decals.integer)
813 VectorCopy(part->vel, v);
815 staintex = part->staintexnum;
816 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
819 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
820 if (part->typeindex == pt_blood && cl_decals.integer)
822 VectorCopy(part->vel, v);
824 staintex = tex_blooddecal[rand()&7];
825 R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
829 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
832 entity_render_t *ent = &cl.entities[hitent].render;
833 unsigned char color[3];
834 if (!cl_decals.integer)
836 if (!ent->allowdecals)
839 l2 = (int)lhrandom(0.5, 256.5);
841 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
842 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
843 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
846 R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
848 R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size);
851 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
858 int besthitent = 0, hitent;
861 for (i = 0;i < 32;i++)
864 VectorMA(org, maxdist, org2, org2);
865 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, 0, 0, collision_extendmovelength.value, true, false, &hitent, false, true);
866 // take the closest trace result that doesn't end up hitting a NOMARKS
867 // surface (sky for example)
868 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
870 bestfrac = trace.fraction;
872 VectorCopy(trace.endpos, bestorg);
873 VectorCopy(trace.plane.normal, bestnormal);
877 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
880 // generates a cubemap name with prefix flags based on info flags (for now only `!`)
881 static char *LightCubemapNumToName(char *vabuf, size_t vasize, int lightcubemapnum, int flags)
883 if (lightcubemapnum <= 0)
885 // `!` is prepended if the cubemap must be nearest-filtered
886 if (flags & PARTICLEEFFECT_FORCENEAREST)
887 return va(vabuf, vasize, "!cubemaps/%i", lightcubemapnum);
888 return va(vabuf, vasize, "cubemaps/%i", lightcubemapnum);
891 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
892 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
893 static void CL_NewParticlesFromEffectinfo(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, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail);
894 static 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, qboolean wanttrail)
897 matrix4x4_t lightmatrix;
900 VectorLerp(originmins, 0.5, originmaxs, center);
901 Matrix4x4_CreateTranslate(&lightmatrix, center[0], center[1], center[2]);
902 if (effectnameindex == EFFECT_SVC_PARTICLE)
904 if (cl_particles.integer)
906 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
908 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
909 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
910 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
913 count *= cl_particles_quality.value;
914 for (;count > 0;count--)
916 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
917 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
922 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
923 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
924 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
925 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
926 else if (effectnameindex == EFFECT_TE_SPIKE)
928 if (cl_particles_bulletimpacts.integer)
930 if (cl_particles_quake.integer)
932 if (cl_particles_smoke.integer)
933 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
937 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
938 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
939 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
943 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
944 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
946 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
948 if (cl_particles_bulletimpacts.integer)
950 if (cl_particles_quake.integer)
952 if (cl_particles_smoke.integer)
953 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
957 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
958 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
959 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
963 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
964 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
965 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
967 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
969 if (cl_particles_bulletimpacts.integer)
971 if (cl_particles_quake.integer)
973 if (cl_particles_smoke.integer)
974 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
978 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
979 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
980 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
984 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
985 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
987 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
989 if (cl_particles_bulletimpacts.integer)
991 if (cl_particles_quake.integer)
993 if (cl_particles_smoke.integer)
994 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
998 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
999 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1000 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1004 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1005 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1006 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1008 else if (effectnameindex == EFFECT_TE_BLOOD)
1010 if (!cl_particles_blood.integer)
1012 if (cl_particles_quake.integer)
1013 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1016 static double bloodaccumulator = 0;
1017 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1018 //CL_NewParticle(center, 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, NULL);
1019 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1020 for (;bloodaccumulator > 0;bloodaccumulator--)
1022 part = CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1023 if (immediatebloodstain && part)
1025 immediatebloodstain = false;
1026 CL_ImmediateBloodStain(part);
1031 else if (effectnameindex == EFFECT_TE_SPARK)
1032 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1033 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1035 // plasma scorch mark
1036 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1037 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1038 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1, 1, 1, 1000, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1040 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1042 if (cl_particles_bulletimpacts.integer)
1044 if (cl_particles_quake.integer)
1045 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1048 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1049 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1050 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1054 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1055 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1057 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1059 if (cl_particles_bulletimpacts.integer)
1061 if (cl_particles_quake.integer)
1062 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1065 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1066 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1067 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1071 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1072 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1073 CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1075 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1077 CL_ParticleExplosion(center);
1078 CL_AllocLightFlash(NULL, &lightmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1080 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1082 CL_ParticleExplosion(center);
1083 CL_AllocLightFlash(NULL, &lightmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1085 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1087 if (cl_particles_quake.integer)
1090 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1093 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1095 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1099 CL_ParticleExplosion(center);
1100 CL_AllocLightFlash(NULL, &lightmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1102 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1103 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2, 2, 2, 1000, 0.2, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1104 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1106 count *= cl_particles_quality.value;
1108 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1110 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1112 float i, j, inc, vel;
1115 inc = 8 / cl_particles_quality.value;
1116 for (i = -128;i < 128;i += inc)
1118 for (j = -128;j < 128;j += inc)
1120 dir[0] = j + lhrandom(0, inc);
1121 dir[1] = i + lhrandom(0, inc);
1123 org[0] = center[0] + dir[0];
1124 org[1] = center[1] + dir[1];
1125 org[2] = center[2] + lhrandom(0, 64);
1126 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1127 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1131 else if (effectnameindex == EFFECT_TE_TELEPORT)
1133 float i, j, k, inc, vel;
1136 if (cl_particles_quake.integer)
1137 inc = 4 / cl_particles_quality.value;
1139 inc = 8 / cl_particles_quality.value;
1140 for (i = -16;i < 16;i += inc)
1142 for (j = -16;j < 16;j += inc)
1144 for (k = -24;k < 32;k += inc)
1146 VectorSet(dir, i*8, j*8, k*8);
1147 VectorNormalize(dir);
1148 vel = lhrandom(50, 113);
1149 if (cl_particles_quake.integer)
1150 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1152 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1156 if (!cl_particles_quake.integer)
1157 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1158 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, NULL, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1160 else if (effectnameindex == EFFECT_TE_TEI_G3)
1161 CL_NewParticle(center, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1162 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1164 if (cl_particles_smoke.integer)
1166 count *= 0.25f * cl_particles_quality.value;
1168 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1171 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1173 CL_ParticleExplosion(center);
1174 CL_AllocLightFlash(NULL, &lightmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, NULL, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1176 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1179 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1180 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1181 if (cl_particles_smoke.integer)
1182 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1183 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1184 if (cl_particles_sparks.integer)
1185 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1186 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1187 CL_AllocLightFlash(NULL, &lightmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1189 else if (effectnameindex == EFFECT_EF_FLAME)
1191 if (!spawnparticles)
1193 count *= 300 * cl_particles_quality.value;
1195 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1196 CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1198 else if (effectnameindex == EFFECT_EF_STARDUST)
1200 if (!spawnparticles)
1202 count *= 200 * cl_particles_quality.value;
1204 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1205 CL_AllocLightFlash(NULL, &lightmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, NULL, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1207 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1211 int smoke, blood, bubbles, r, color, spawnedcount;
1213 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1216 Vector4Set(light, 0, 0, 0, 0);
1218 if (effectnameindex == EFFECT_TR_ROCKET)
1219 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1220 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1222 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1223 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1225 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1227 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1228 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1232 matrix4x4_t traillightmatrix;
1233 Matrix4x4_CreateFromQuakeEntity(&traillightmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1234 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &traillightmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1235 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1239 if (!spawnparticles)
1242 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1245 VectorSubtract(originmaxs, originmins, dir);
1246 len = VectorNormalizeLength(dir);
1250 dec = -ent->persistent.trail_time;
1251 ent->persistent.trail_time += len;
1252 if (ent->persistent.trail_time < 0.01f)
1255 // if we skip out, leave it reset
1256 ent->persistent.trail_time = 0.0f;
1261 // advance into this frame to reach the first puff location
1262 VectorMA(originmins, dec, dir, pos);
1265 smoke = cl_particles.integer && cl_particles_smoke.integer;
1266 blood = cl_particles.integer && cl_particles_blood.integer;
1267 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1268 qd = 1.0f / cl_particles_quality.value;
1271 while (len >= 0 && ++spawnedcount <= 16384)
1276 if (effectnameindex == EFFECT_TR_BLOOD)
1278 if (cl_particles_quake.integer)
1280 color = particlepalette[67 + (rand()&3)];
1281 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1286 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1289 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1291 if (cl_particles_quake.integer)
1294 color = particlepalette[67 + (rand()&3)];
1295 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1300 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1306 if (effectnameindex == EFFECT_TR_ROCKET)
1308 if (cl_particles_quake.integer)
1311 color = particlepalette[ramp3[r]];
1312 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1316 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1317 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1320 else if (effectnameindex == EFFECT_TR_GRENADE)
1322 if (cl_particles_quake.integer)
1325 color = particlepalette[ramp3[r]];
1326 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1330 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1333 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1335 if (cl_particles_quake.integer)
1338 color = particlepalette[52 + (rand()&7)];
1339 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1340 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1342 else if (gamemode == GAME_GOODVSBAD2)
1345 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1349 color = particlepalette[20 + (rand()&7)];
1350 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1353 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1355 if (cl_particles_quake.integer)
1358 color = particlepalette[230 + (rand()&7)];
1359 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1360 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1364 color = particlepalette[226 + (rand()&7)];
1365 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1368 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1370 if (cl_particles_quake.integer)
1372 color = particlepalette[152 + (rand()&3)];
1373 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1375 else if (gamemode == GAME_GOODVSBAD2)
1378 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1380 else if (gamemode == GAME_PRYDON)
1383 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1386 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1388 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1391 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1393 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1396 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1398 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1399 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1403 if (effectnameindex == EFFECT_TR_ROCKET)
1404 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1405 else if (effectnameindex == EFFECT_TR_GRENADE)
1406 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1408 // advance to next time and position
1411 VectorMA (pos, dec, dir, pos);
1414 ent->persistent.trail_time = len;
1417 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1420 // this is also called on point effects with spawndlight = true and
1421 // spawnparticles = true
1422 static void CL_NewParticlesFromEffectinfo(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, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail)
1424 qboolean found = false;
1426 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1428 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1429 return; // no such effect
1431 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1433 int effectinfoindex;
1436 particleeffectinfo_t *info;
1448 qboolean underwater;
1449 qboolean immediatebloodstain;
1451 float avgtint[4], tint[4], tintlerp;
1452 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1453 VectorLerp(originmins, 0.5, originmaxs, center);
1454 supercontents = CL_PointSuperContents(center);
1455 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1456 VectorSubtract(originmaxs, originmins, traildir);
1457 traillen = VectorLength(traildir);
1458 VectorNormalize(traildir);
1461 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1465 Vector4Set(avgtint, 1, 1, 1, 1);
1467 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1469 if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1471 qboolean definedastrail = info->trailspacing > 0;
1473 qboolean drawastrail = wanttrail;
1474 if (cl_particles_forcetraileffects.integer)
1475 drawastrail = drawastrail || definedastrail;
1478 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1480 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1483 // spawn a dlight if requested
1484 if (info->lightradiusstart > 0 && spawndlight)
1486 matrix4x4_t tempmatrix;
1488 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1490 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1491 if (info->lighttime > 0 && info->lightradiusfade > 0)
1493 // light flash (explosion, etc)
1494 // called when effect starts
1495 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, LightCubemapNumToName(vabuf, sizeof(vabuf), info->lightcubemapnum, info->flags), -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1497 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1500 // called by CL_LinkNetworkEntity
1501 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1502 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1503 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1504 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1505 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, LightCubemapNumToName(vabuf, sizeof(vabuf), info->lightcubemapnum, info->flags), info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1506 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1510 if (!spawnparticles)
1515 if (info->tex[1] > info->tex[0])
1517 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1518 tex = min(tex, info->tex[1] - 1);
1520 if(info->staintex[0] < 0)
1521 staintex = info->staintex[0];
1524 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1525 staintex = min(staintex, info->staintex[1] - 1);
1527 if (info->particletype == pt_decal)
1529 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1530 AnglesFromVectors(angles, velocity, NULL, false);
1531 AngleVectors(angles, forward, right, up);
1532 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1534 CL_SpawnDecalParticleForPoint(trailpos, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]);
1536 else if (info->orientation == PARTICLE_HBEAM)
1541 AnglesFromVectors(angles, traildir, NULL, false);
1542 AngleVectors(angles, forward, right, up);
1543 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1545 CL_NewParticle(center, 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] + trailpos[0], originmins[1] + trailpos[1], originmins[2] + trailpos[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, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL);
1550 if (!cl_particles.integer)
1552 switch (info->particletype)
1554 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1555 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1556 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1557 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1558 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1559 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1563 cnt = info->countabsolute;
1564 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1565 // if drawastrail is not set, we will
1566 // use the regular cnt-based random
1567 // particle spawning at the center; so
1568 // do NOT apply trailspacing then!
1569 if (drawastrail && definedastrail)
1570 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1573 continue; // nothing to draw
1574 info->particleaccumulator += cnt;
1576 if (drawastrail || definedastrail)
1577 immediatebloodstain = false;
1579 immediatebloodstain =
1580 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1582 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1586 VectorCopy(originmins, trailpos);
1587 trailstep = traillen / cnt;
1591 VectorCopy(center, trailpos);
1597 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1598 AnglesFromVectors(angles, velocity, NULL, false);
1601 AnglesFromVectors(angles, traildir, NULL, false);
1603 AngleVectors(angles, forward, right, up);
1604 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1605 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1606 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1607 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1609 if (info->tex[1] > info->tex[0])
1611 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1612 tex = min(tex, info->tex[1] - 1);
1614 if (!(drawastrail || definedastrail))
1616 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1617 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1618 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1622 tintlerp = lhrandom(0, 1);
1623 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1626 part = CL_NewParticle(center, 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] + velocity[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1] + velocity[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2] + velocity[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, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL);
1627 if (immediatebloodstain && part)
1629 immediatebloodstain = false;
1630 CL_ImmediateBloodStain(part);
1633 VectorMA(trailpos, trailstep, traildir, trailpos);
1640 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1643 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, float tintmins[4], float tintmaxs[4], float fade)
1645 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1648 void CL_ParticleBox(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, float tintmins[4], float tintmaxs[4], float fade)
1650 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1653 // note: this one ONLY does boxes!
1654 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)
1656 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1664 void CL_EntityParticles (const entity_t *ent)
1667 vec_t pitch, yaw, dist = 64, beamlength = 16;
1669 static vec3_t avelocities[NUMVERTEXNORMALS];
1670 if (!cl_particles.integer) return;
1671 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1673 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1675 if (!avelocities[0][0])
1676 for (i = 0;i < NUMVERTEXNORMALS;i++)
1677 for (j = 0;j < 3;j++)
1678 avelocities[i][j] = lhrandom(0, 2.55);
1680 for (i = 0;i < NUMVERTEXNORMALS;i++)
1682 yaw = cl.time * avelocities[i][0];
1683 pitch = cl.time * avelocities[i][1];
1684 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1685 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1686 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1687 CL_NewParticle(org, 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, 1, 1, 0, 0, NULL);
1692 void CL_ReadPointFile_f(cmd_state_t *cmd)
1694 double org[3], leakorg[3];
1697 char *pointfile = NULL, *pointfilepos, *t, tchar;
1698 char name[MAX_QPATH];
1703 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1704 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1707 Con_Printf("Could not open %s\n", name);
1711 Con_Printf("Reading %s...\n", name);
1712 VectorClear(leakorg);
1715 pointfilepos = pointfile;
1716 while (*pointfilepos)
1718 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1723 while (*t && *t != '\n' && *t != '\r')
1727 #if _MSC_VER >= 1400
1728 #define sscanf sscanf_s
1730 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1731 VectorCopy(org, vecorg);
1737 VectorCopy(org, leakorg);
1740 if (cl.num_particles < cl.max_particles - 3)
1743 CL_NewParticle(vecorg, 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, 1, 1, 0, 0, NULL);
1746 Mem_Free(pointfile);
1747 VectorCopy(leakorg, vecorg);
1748 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1755 CL_NewParticle(vecorg, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1756 CL_NewParticle(vecorg, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1757 CL_NewParticle(vecorg, 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_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL);
1762 CL_ParseParticleEffect
1764 Parse an effect out of the server message
1767 void CL_ParseParticleEffect (void)
1770 int i, count, msgcount, color;
1772 MSG_ReadVector(&cl_message, org, cls.protocol);
1773 for (i=0 ; i<3 ; i++)
1774 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1775 msgcount = MSG_ReadByte(&cl_message);
1776 color = MSG_ReadByte(&cl_message);
1778 if (msgcount == 255)
1783 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1788 CL_ParticleExplosion
1792 void CL_ParticleExplosion (const vec3_t org)
1798 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1799 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1801 if (cl_particles_quake.integer)
1803 for (i = 0;i < 1024;i++)
1809 color = particlepalette[ramp1[r]];
1810 CL_NewParticle(org, 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, 1, 1, 0, 0, NULL);
1814 color = particlepalette[ramp2[r]];
1815 CL_NewParticle(org, 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, 1, 1, 0, 0, NULL);
1821 i = CL_PointSuperContents(org);
1822 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1824 if (cl_particles.integer && cl_particles_bubbles.integer)
1825 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1826 CL_NewParticle(org, 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, 1, 1, 0, 0, NULL);
1830 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1832 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1839 VectorMA(org, 128, v2, v);
1840 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, 0, 0, collision_extendmovelength.value, true, false, NULL, false, false);
1842 while (k++ < 16 && trace.fraction < 0.1f);
1843 VectorSubtract(trace.endpos, org, v2);
1844 VectorScale(v2, 2.0f, v2);
1845 CL_NewParticle(org, 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, 1, 1, 0, 0, NULL);
1851 if (cl_particles_explosions_shell.integer)
1852 R_NewExplosion(org);
1857 CL_ParticleExplosion2
1861 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1864 if (!cl_particles.integer) return;
1866 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1868 k = particlepalette[colorStart + (i % colorLength)];
1869 if (cl_particles_quake.integer)
1870 CL_NewParticle(org, 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, 1, 1, 0, 0, NULL);
1872 CL_NewParticle(org, 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, 1, 1, 0, 0, NULL);
1876 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1879 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1880 if (cl_particles_sparks.integer)
1882 sparkcount *= cl_particles_quality.value;
1883 while(sparkcount-- > 0)
1884 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1888 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1891 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1892 if (cl_particles_smoke.integer)
1894 smokecount *= cl_particles_quality.value;
1895 while(smokecount-- > 0)
1896 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1900 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)
1904 if (!cl_particles.integer) return;
1905 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1907 count = (int)(count * cl_particles_quality.value);
1910 k = particlepalette[colorbase + (rand()&3)];
1911 CL_NewParticle(center, 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, 1, 1, 0, 0, NULL);
1915 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1918 float minz, maxz, lifetime = 30;
1920 if (!cl_particles.integer) return;
1921 if (dir[2] < 0) // falling
1923 minz = maxs[2] + dir[2] * 0.1;
1926 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1931 maxz = maxs[2] + dir[2] * 0.1;
1933 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1936 count = (int)(count * cl_particles_quality.value);
1941 if (!cl_particles_rain.integer) break;
1942 count *= 4; // ick, this should be in the mod or maps?
1946 k = particlepalette[colorbase + (rand()&3)];
1947 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1948 if (gamemode == GAME_GOODVSBAD2)
1949 CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1951 CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL);
1955 if (!cl_particles_snow.integer) break;
1958 k = particlepalette[colorbase + (rand()&3)];
1959 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1960 if (gamemode == GAME_GOODVSBAD2)
1961 CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1963 CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1967 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1971 cvar_t r_drawparticles = {CVAR_CLIENT, "r_drawparticles", "1", "enables drawing of particles"};
1972 static cvar_t r_drawparticles_drawdistance = {CVAR_CLIENT | CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1973 static cvar_t r_drawparticles_nearclip_min = {CVAR_CLIENT | CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
1974 static cvar_t r_drawparticles_nearclip_max = {CVAR_CLIENT | CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
1975 cvar_t r_drawdecals = {CVAR_CLIENT, "r_drawdecals", "1", "enables drawing of decals"};
1976 static cvar_t r_drawdecals_drawdistance = {CVAR_CLIENT | CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1978 #define PARTICLETEXTURESIZE 64
1979 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1981 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1985 dz = 1 - (dx*dx+dy*dy);
1986 if (dz > 0) // it does hit the sphere
1990 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1991 VectorNormalize(normal);
1992 dot = DotProduct(normal, light);
1993 if (dot > 0.5) // interior reflection
1994 f += ((dot * 2) - 1);
1995 else if (dot < -0.5) // exterior reflection
1996 f += ((dot * -2) - 1);
1998 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1999 VectorNormalize(normal);
2000 dot = DotProduct(normal, light);
2001 if (dot > 0.5) // interior reflection
2002 f += ((dot * 2) - 1);
2003 else if (dot < -0.5) // exterior reflection
2004 f += ((dot * -2) - 1);
2006 f += 16; // just to give it a haze so you can see the outline
2007 f = bound(0, f, 255);
2008 return (unsigned char) f;
2014 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2015 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2017 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2018 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2019 *width = particlefontcellwidth;
2020 *height = particlefontcellheight;
2023 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2025 int basex, basey, w, h, y;
2026 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2027 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2028 Sys_Error("invalid particle texture size for autogenerating");
2029 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2030 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2033 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2036 float cx, cy, dx, dy, f, iradius;
2038 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2039 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2040 iradius = 1.0f / radius;
2041 alpha *= (1.0f / 255.0f);
2042 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2044 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2048 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2053 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2054 d[0] += (int)(f * (blue - d[0]));
2055 d[1] += (int)(f * (green - d[1]));
2056 d[2] += (int)(f * (red - d[2]));
2063 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2066 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2068 data[0] = bound(minb, data[0], maxb);
2069 data[1] = bound(ming, data[1], maxg);
2070 data[2] = bound(minr, data[2], maxr);
2075 static void particletextureinvert(unsigned char *data)
2078 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2080 data[0] = 255 - data[0];
2081 data[1] = 255 - data[1];
2082 data[2] = 255 - data[2];
2086 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2087 static void R_InitBloodTextures (unsigned char *particletexturedata)
2090 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2091 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2094 for (i = 0;i < 8;i++)
2096 memset(data, 255, datasize);
2097 for (k = 0;k < 24;k++)
2098 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2099 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2100 particletextureinvert(data);
2101 setuptex(tex_bloodparticle[i], data, particletexturedata);
2105 for (i = 0;i < 8;i++)
2107 memset(data, 255, datasize);
2109 for (j = 1;j < 10;j++)
2110 for (k = min(j, m - 1);k < m;k++)
2111 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2112 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2113 particletextureinvert(data);
2114 setuptex(tex_blooddecal[i], data, particletexturedata);
2120 //uncomment this to make engine save out particle font to a tga file when run
2121 //#define DUMPPARTICLEFONT
2123 static void R_InitParticleTexture (void)
2125 int x, y, d, i, k, m;
2126 int basex, basey, w, h;
2127 float dx, dy, f, s1, t1, s2, t2;
2130 fs_offset_t filesize;
2131 char texturename[MAX_QPATH];
2134 // a note: decals need to modulate (multiply) the background color to
2135 // properly darken it (stain), and they need to be able to alpha fade,
2136 // this is a very difficult challenge because it means fading to white
2137 // (no change to background) rather than black (darkening everything
2138 // behind the whole decal polygon), and to accomplish this the texture is
2139 // inverted (dark red blood on white background becomes brilliant cyan
2140 // and white on black background) so we can alpha fade it to black, then
2141 // we invert it again during the blendfunc to make it work...
2143 #ifndef DUMPPARTICLEFONT
2144 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false, false);
2147 particlefonttexture = decalskinframe->base;
2148 // TODO maybe allow custom grid size?
2149 particlefontwidth = image_width;
2150 particlefontheight = image_height;
2151 particlefontcellwidth = image_width / 8;
2152 particlefontcellheight = image_height / 8;
2153 particlefontcols = 8;
2154 particlefontrows = 8;
2159 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2160 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2161 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2162 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2163 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2165 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2166 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2167 particlefontcols = 8;
2168 particlefontrows = 8;
2170 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2173 for (i = 0;i < 8;i++)
2175 memset(data, 255, datasize);
2178 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2179 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2181 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2183 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2184 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2186 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2187 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2189 d = (int)(d * (1-(dx*dx+dy*dy)));
2190 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2191 d = bound(0, d, 255);
2192 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2199 setuptex(tex_smoke[i], data, particletexturedata);
2203 memset(data, 255, datasize);
2204 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2206 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2207 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2209 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2210 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2211 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2214 setuptex(tex_rainsplash, data, particletexturedata);
2217 memset(data, 255, datasize);
2218 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2220 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2221 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2223 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2224 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2225 d = bound(0, d, 255);
2226 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2229 setuptex(tex_particle, data, particletexturedata);
2232 memset(data, 255, datasize);
2233 light[0] = 1;light[1] = 1;light[2] = 1;
2234 VectorNormalize(light);
2235 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2237 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2238 // stretch upper half of bubble by +50% and shrink lower half by -50%
2239 // (this gives an elongated teardrop shape)
2241 dy = (dy - 0.5f) * 2.0f;
2243 dy = (dy - 0.5f) / 1.5f;
2244 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2246 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2247 // shrink bubble width to half
2249 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2252 setuptex(tex_raindrop, data, particletexturedata);
2255 memset(data, 255, datasize);
2256 light[0] = 1;light[1] = 1;light[2] = 1;
2257 VectorNormalize(light);
2258 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2260 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2261 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2263 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2264 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2267 setuptex(tex_bubble, data, particletexturedata);
2269 // Blood particles and blood decals
2270 R_InitBloodTextures (particletexturedata);
2273 for (i = 0;i < 8;i++)
2275 memset(data, 255, datasize);
2276 for (k = 0;k < 12;k++)
2277 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2278 for (k = 0;k < 3;k++)
2279 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2280 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2281 particletextureinvert(data);
2282 setuptex(tex_bulletdecal[i], data, particletexturedata);
2285 #ifdef DUMPPARTICLEFONT
2286 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2289 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, 0, 0, 0, false);
2290 particlefonttexture = decalskinframe->base;
2292 Mem_Free(particletexturedata);
2297 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2299 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2300 particletexture[i].texture = particlefonttexture;
2301 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2302 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2303 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2304 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2307 #ifndef DUMPPARTICLEFONT
2308 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2309 if (!particletexture[tex_beam].texture)
2312 unsigned char noise3[64][64], data2[64][16][4];
2314 fractalnoise(&noise3[0][0], 64, 4);
2316 for (y = 0;y < 64;y++)
2318 dy = (y - 0.5f*64) / (64*0.5f-1);
2319 for (x = 0;x < 16;x++)
2321 dx = (x - 0.5f*16) / (16*0.5f-2);
2322 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2323 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2324 data2[y][x][3] = 255;
2328 #ifdef DUMPPARTICLEFONT
2329 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2331 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2333 particletexture[tex_beam].s1 = 0;
2334 particletexture[tex_beam].t1 = 0;
2335 particletexture[tex_beam].s2 = 1;
2336 particletexture[tex_beam].t2 = 1;
2338 // now load an texcoord/texture override file
2339 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2346 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2348 if(!strcmp(com_token, "\n"))
2349 continue; // empty line
2350 i = atoi(com_token);
2358 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2360 strlcpy(texturename, com_token, sizeof(texturename));
2361 s1 = atof(com_token);
2362 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2365 t1 = atof(com_token);
2366 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2368 s2 = atof(com_token);
2369 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2371 t2 = atof(com_token);
2372 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2373 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2374 strlcpy(texturename, com_token, sizeof(texturename));
2381 if (!texturename[0])
2383 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2386 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2388 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2391 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, true); // note: this loads as sRGB if sRGB is active!
2392 particletexture[i].texture = sf->base;
2393 particletexture[i].s1 = s1;
2394 particletexture[i].t1 = t1;
2395 particletexture[i].s2 = s2;
2396 particletexture[i].t2 = t2;
2402 static void r_part_start(void)
2405 // generate particlepalette for convenience from the main one
2406 for (i = 0;i < 256;i++)
2407 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2408 particletexturepool = R_AllocTexturePool();
2409 R_InitParticleTexture ();
2410 CL_Particles_LoadEffectInfo(NULL);
2413 static void r_part_shutdown(void)
2415 R_FreeTexturePool(&particletexturepool);
2418 static void r_part_newmap(void)
2421 R_SkinFrame_MarkUsed(decalskinframe);
2422 CL_Particles_LoadEffectInfo(NULL);
2425 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2426 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2428 void R_Particles_Init (void)
2431 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2433 particle_elements[i*6+0] = i*4+0;
2434 particle_elements[i*6+1] = i*4+1;
2435 particle_elements[i*6+2] = i*4+2;
2436 particle_elements[i*6+3] = i*4+0;
2437 particle_elements[i*6+4] = i*4+2;
2438 particle_elements[i*6+5] = i*4+3;
2441 Cvar_RegisterVariable(&r_drawparticles);
2442 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2443 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2444 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2445 Cvar_RegisterVariable(&r_drawdecals);
2446 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2447 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2450 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2452 vec3_t vecorg, vecvel, baseright, baseup;
2453 int surfacelistindex;
2454 int batchstart, batchcount;
2455 const particle_t *p;
2457 rtexture_t *texture;
2458 float *v3f, *t2f, *c4f;
2459 particletexture_t *tex;
2460 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2461 // float ambient[3], diffuse[3], diffusenormal[3];
2462 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2463 vec4_t colormultiplier;
2464 float minparticledist_start, minparticledist_end;
2467 RSurf_ActiveModelEntity(r_refdef.scene.worldentity, false, false, false);
2469 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));
2471 r_refdef.stats[r_stat_particles] += numsurfaces;
2472 // R_Mesh_ResetTextureState();
2473 GL_DepthMask(false);
2474 GL_DepthRange(0, 1);
2475 GL_PolygonOffset(0, 0);
2477 GL_CullFace(GL_NONE);
2479 spintime = r_refdef.scene.time;
2481 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2482 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2483 dofade = (minparticledist_start < minparticledist_end);
2485 // first generate all the vertices at once
2486 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2488 p = cl.particles + surfacelist[surfacelistindex];
2490 blendmode = (pblend_t)p->blendmode;
2492 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2493 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2494 alpha = palpha * colormultiplier[3];
2495 // ensure alpha multiplier saturates properly
2501 case PBLEND_INVALID:
2503 // additive and modulate can just fade out in fog (this is correct)
2504 if (r_refdef.fogenabled)
2505 alpha *= RSurf_FogVertex(p->org);
2506 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2507 alpha *= 1.0f / 256.0f;
2508 c4f[0] = p->color[0] * alpha;
2509 c4f[1] = p->color[1] * alpha;
2510 c4f[2] = p->color[2] * alpha;
2514 // additive and modulate can just fade out in fog (this is correct)
2515 if (r_refdef.fogenabled)
2516 alpha *= RSurf_FogVertex(p->org);
2517 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2518 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2519 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2520 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2524 c4f[0] = p->color[0] * colormultiplier[0];
2525 c4f[1] = p->color[1] * colormultiplier[1];
2526 c4f[2] = p->color[2] * colormultiplier[2];
2528 // note: lighting is not cheap!
2529 if (particletype[p->typeindex].lighting)
2531 float a[3], c[3], dir[3];
2532 vecorg[0] = p->org[0];
2533 vecorg[1] = p->org[1];
2534 vecorg[2] = p->org[2];
2535 R_CompleteLightPoint(a, c, dir, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT, r_refdef.scene.lightmapintensity, r_refdef.scene.ambientintensity);
2536 c4f[0] = p->color[0] * colormultiplier[0] * (a[0] + 0.25f * c[0]);
2537 c4f[1] = p->color[1] * colormultiplier[1] * (a[1] + 0.25f * c[1]);
2538 c4f[2] = p->color[2] * colormultiplier[2] * (a[2] + 0.25f * c[2]);
2540 // mix in the fog color
2541 if (r_refdef.fogenabled)
2543 fog = RSurf_FogVertex(p->org);
2545 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2546 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2547 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2549 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2550 VectorScale(c4f, alpha, c4f);
2553 // copy the color into the other three vertices
2554 Vector4Copy(c4f, c4f + 4);
2555 Vector4Copy(c4f, c4f + 8);
2556 Vector4Copy(c4f, c4f + 12);
2558 size = p->size * cl_particles_size.value;
2559 tex = &particletexture[p->texnum];
2560 switch(p->orientation)
2562 // case PARTICLE_INVALID:
2563 case PARTICLE_BILLBOARD:
2564 if (p->angle + p->spin)
2566 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2567 spinsin = sin(spinrad) * size;
2568 spincos = cos(spinrad) * size;
2569 spinm1 = -p->stretch * spincos;
2572 spinm4 = -p->stretch * spincos;
2573 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2574 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2578 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2579 VectorScale(r_refdef.view.up, size, up);
2582 v3f[ 0] = p->org[0] - right[0] - up[0];
2583 v3f[ 1] = p->org[1] - right[1] - up[1];
2584 v3f[ 2] = p->org[2] - right[2] - up[2];
2585 v3f[ 3] = p->org[0] - right[0] + up[0];
2586 v3f[ 4] = p->org[1] - right[1] + up[1];
2587 v3f[ 5] = p->org[2] - right[2] + up[2];
2588 v3f[ 6] = p->org[0] + right[0] + up[0];
2589 v3f[ 7] = p->org[1] + right[1] + up[1];
2590 v3f[ 8] = p->org[2] + right[2] + up[2];
2591 v3f[ 9] = p->org[0] + right[0] - up[0];
2592 v3f[10] = p->org[1] + right[1] - up[1];
2593 v3f[11] = p->org[2] + right[2] - up[2];
2594 t2f[0] = tex->s1;t2f[1] = tex->t2;
2595 t2f[2] = tex->s1;t2f[3] = tex->t1;
2596 t2f[4] = tex->s2;t2f[5] = tex->t1;
2597 t2f[6] = tex->s2;t2f[7] = tex->t2;
2599 case PARTICLE_ORIENTED_DOUBLESIDED:
2600 vecvel[0] = p->vel[0];
2601 vecvel[1] = p->vel[1];
2602 vecvel[2] = p->vel[2];
2603 VectorVectors(vecvel, baseright, baseup);
2604 if (p->angle + p->spin)
2606 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2607 spinsin = sin(spinrad) * size;
2608 spincos = cos(spinrad) * size;
2609 spinm1 = p->stretch * spincos;
2612 spinm4 = p->stretch * spincos;
2613 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2614 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2618 VectorScale(baseright, size * p->stretch, right);
2619 VectorScale(baseup, size, up);
2621 v3f[ 0] = p->org[0] - right[0] - up[0];
2622 v3f[ 1] = p->org[1] - right[1] - up[1];
2623 v3f[ 2] = p->org[2] - right[2] - up[2];
2624 v3f[ 3] = p->org[0] - right[0] + up[0];
2625 v3f[ 4] = p->org[1] - right[1] + up[1];
2626 v3f[ 5] = p->org[2] - right[2] + up[2];
2627 v3f[ 6] = p->org[0] + right[0] + up[0];
2628 v3f[ 7] = p->org[1] + right[1] + up[1];
2629 v3f[ 8] = p->org[2] + right[2] + up[2];
2630 v3f[ 9] = p->org[0] + right[0] - up[0];
2631 v3f[10] = p->org[1] + right[1] - up[1];
2632 v3f[11] = p->org[2] + right[2] - up[2];
2633 t2f[0] = tex->s1;t2f[1] = tex->t2;
2634 t2f[2] = tex->s1;t2f[3] = tex->t1;
2635 t2f[4] = tex->s2;t2f[5] = tex->t1;
2636 t2f[6] = tex->s2;t2f[7] = tex->t2;
2638 case PARTICLE_SPARK:
2639 len = VectorLength(p->vel);
2640 VectorNormalize2(p->vel, up);
2641 lenfactor = p->stretch * 0.04 * len;
2642 if(lenfactor < size * 0.5)
2643 lenfactor = size * 0.5;
2644 VectorMA(p->org, -lenfactor, up, v);
2645 VectorMA(p->org, lenfactor, up, up2);
2646 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2647 t2f[0] = tex->s1;t2f[1] = tex->t2;
2648 t2f[2] = tex->s1;t2f[3] = tex->t1;
2649 t2f[4] = tex->s2;t2f[5] = tex->t1;
2650 t2f[6] = tex->s2;t2f[7] = tex->t2;
2652 case PARTICLE_VBEAM:
2653 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2654 VectorSubtract(p->vel, p->org, up);
2655 VectorNormalize(up);
2656 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2657 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2658 t2f[0] = tex->s2;t2f[1] = v[0];
2659 t2f[2] = tex->s1;t2f[3] = v[0];
2660 t2f[4] = tex->s1;t2f[5] = v[1];
2661 t2f[6] = tex->s2;t2f[7] = v[1];
2663 case PARTICLE_HBEAM:
2664 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2665 VectorSubtract(p->vel, p->org, up);
2666 VectorNormalize(up);
2667 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2668 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2669 t2f[0] = v[0];t2f[1] = tex->t1;
2670 t2f[2] = v[0];t2f[3] = tex->t2;
2671 t2f[4] = v[1];t2f[5] = tex->t2;
2672 t2f[6] = v[1];t2f[7] = tex->t1;
2675 if (r_showparticleedges.integer)
2677 R_DebugLine(v3f, v3f + 3);
2678 R_DebugLine(v3f + 3, v3f + 6);
2679 R_DebugLine(v3f + 6, v3f + 9);
2680 R_DebugLine(v3f + 9, v3f);
2684 // now render batches of particles based on blendmode and texture
2685 blendmode = PBLEND_INVALID;
2689 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2690 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2692 p = cl.particles + surfacelist[surfacelistindex];
2694 if (texture != particletexture[p->texnum].texture)
2696 texture = particletexture[p->texnum].texture;
2697 R_SetupShader_Generic(texture, false, false, false);
2700 if (p->blendmode == PBLEND_INVMOD)
2702 // inverse modulate blend - group these
2703 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2704 // iterate until we find a change in settings
2705 batchstart = surfacelistindex++;
2706 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2708 p = cl.particles + surfacelist[surfacelistindex];
2709 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2715 // additive or alpha blend - group these
2716 // (we can group these because we premultiplied the texture alpha)
2717 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2718 // iterate until we find a change in settings
2719 batchstart = surfacelistindex++;
2720 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2722 p = cl.particles + surfacelist[surfacelistindex];
2723 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2728 batchcount = surfacelistindex - batchstart;
2729 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2733 void R_DrawParticles (void)
2736 int drawparticles = r_drawparticles.integer;
2737 float minparticledist_start;
2739 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2745 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2746 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2748 // LadyHavoc: early out conditions
2749 if (!cl.num_particles)
2752 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2753 gravity = frametime * cl.movevars_gravity;
2754 update = frametime > 0;
2755 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2756 drawdist2 = drawdist2*drawdist2;
2758 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2762 if (cl.free_particle > i)
2763 cl.free_particle = i;
2769 if (p->delayedspawn > cl.time)
2772 p->size += p->sizeincrease * frametime;
2773 p->alpha -= p->alphafade * frametime;
2775 if (p->alpha <= 0 || p->die <= cl.time)
2778 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2780 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2782 if (p->typeindex == pt_blood)
2783 p->size += frametime * 8;
2785 p->vel[2] -= p->gravity * gravity;
2786 f = 1.0f - min(p->liquidfriction * frametime, 1);
2787 VectorScale(p->vel, f, p->vel);
2791 p->vel[2] -= p->gravity * gravity;
2794 f = 1.0f - min(p->airfriction * frametime, 1);
2795 VectorScale(p->vel, f, p->vel);
2799 VectorCopy(p->org, oldorg);
2800 VectorMA(p->org, frametime, p->vel, p->org);
2801 // if (p->bounce && cl.time >= p->delayedcollisions)
2802 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2804 trace = CL_TraceLine(oldorg, p->org, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), 0, 0, collision_extendmovelength.value, true, false, &hitent, false, false);
2805 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2806 // or if the trace hit something flagged as NOIMPACT
2807 // then remove the particle
2808 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2810 VectorCopy(trace.endpos, p->org);
2811 // react if the particle hit something
2812 if (trace.fraction < 1)
2814 VectorCopy(trace.endpos, p->org);
2816 if (p->staintexnum >= 0)
2818 // blood - splash on solid
2819 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2822 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2823 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2824 if (cl_decals.integer)
2826 // create a decal for the blood splat
2827 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2828 if (cl_decals_newsystem_bloodsmears.integer)
2830 VectorCopy(p->vel, decaldir);
2831 VectorNormalize(decaldir);
2834 VectorCopy(trace.plane.normal, decaldir);
2835 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2840 if (p->typeindex == pt_blood)
2842 // blood - splash on solid
2843 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2845 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2847 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)));
2848 if (cl_decals.integer)
2850 // create a decal for the blood splat
2851 if (cl_decals_newsystem_bloodsmears.integer)
2853 VectorCopy(p->vel, decaldir);
2854 VectorNormalize(decaldir);
2857 VectorCopy(trace.plane.normal, decaldir);
2858 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, 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 * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
2863 else if (p->bounce < 0)
2865 // bounce -1 means remove on impact
2870 // anything else - bounce off solid
2871 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2872 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2877 if (VectorLength2(p->vel) < 0.03)
2879 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
2881 VectorClear(p->vel);
2885 if (p->typeindex != pt_static)
2887 switch (p->typeindex)
2889 case pt_entityparticle:
2890 // particle that removes itself after one rendered frame
2897 a = CL_PointSuperContents(p->org);
2898 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2902 a = CL_PointSuperContents(p->org);
2903 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2907 a = CL_PointSuperContents(p->org);
2908 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
2912 if (cl.time > p->time2)
2915 p->time2 = cl.time + (rand() & 3) * 0.1;
2916 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2917 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2919 a = CL_PointSuperContents(p->org);
2920 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LIQUIDSMASK))
2928 else if (p->delayedspawn > cl.time)
2932 // don't render particles too close to the view (they chew fillrate)
2933 // also don't render particles behind the view (useless)
2934 // further checks to cull to the frustum would be too slow here
2935 switch(p->typeindex)
2938 // beams have no culling
2939 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2942 if(cl_particles_visculling.integer)
2943 if (!r_refdef.viewcache.world_novis)
2944 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2946 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2948 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2951 // anything else just has to be in front of the viewer and visible at this distance
2952 if (!r_refdef.view.useperspective || (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size)))
2953 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2960 if (cl.free_particle > i)
2961 cl.free_particle = i;
2964 // reduce cl.num_particles if possible
2965 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2968 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2970 particle_t *oldparticles = cl.particles;
2971 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2972 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2973 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2974 Mem_Free(oldparticles);