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_DEFINED 2147483648U
49 typedef struct particleeffectinfo_s
51 int effectnameindex; // which effect this belongs to
52 // PARTICLEEFFECT_* bits
54 // blood effects may spawn very few particles, so proper fraction-overflow
55 // handling is very important, this variable keeps track of the fraction
56 double particleaccumulator;
57 // the math is: countabsolute + requestedcount * countmultiplier * quality
58 // absolute number of particles to spawn, often used for decals
59 // (unaffected by quality and requestedcount)
61 // multiplier for the number of particles CL_ParticleEffect was told to
62 // spawn, most effects do not really have a count and hence use 1, so
63 // this is often the actual count to spawn, not merely a multiplier
64 float countmultiplier;
65 // if > 0 this causes the particle to spawn in an evenly spaced line from
66 // originmins to originmaxs (causing them to describe a trail, not a box)
68 // type of particle to spawn (defines some aspects of behavior)
70 // blending mode used on this particle type
72 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
73 porientation_t orientation;
74 // range of colors to choose from in hex RRGGBB (like HTML color tags),
75 // randomly interpolated at spawn
76 unsigned int color[2];
77 // a random texture is chosen in this range (note the second value is one
78 // past the last choosable, so for example 8,16 chooses any from 8 up and
80 // if start and end of the range are the same, no randomization is done
82 // range of size values randomly chosen when spawning, plus size increase over time
84 // range of alpha values randomly chosen when spawning, plus alpha fade
86 // how long the particle should live (note it is also removed if alpha drops to 0)
88 // how much gravity affects this particle (negative makes it fly up!)
90 // how much bounce the particle has when it hits a surface
91 // if negative the particle is removed on impact
93 // if in air this friction is applied
94 // if negative the particle accelerates
96 // if in liquid (water/slime/lava) this friction is applied
97 // if negative the particle accelerates
99 // these offsets are added to the values given to particleeffect(), and
100 // then an ellipsoid-shaped jitter is added as defined by these
101 // (they are the 3 radii)
103 // stretch velocity factor (used for sparks)
104 float originoffset[3];
105 float relativeoriginoffset[3];
106 float velocityoffset[3];
107 float relativevelocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 float lightcorona[2];
119 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
124 float rotate[4]; // min/max base angle, min/max rotation over time
126 particleeffectinfo_t;
128 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
130 int numparticleeffectinfo;
131 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
133 static int particlepalette[256];
135 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
136 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
137 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
138 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
139 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
140 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
141 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
142 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
143 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
144 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
145 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
146 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
147 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
148 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
149 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
150 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
151 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
152 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
153 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
154 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
155 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
156 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
157 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
158 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
159 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
160 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
161 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
162 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
163 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
164 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
165 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
166 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
169 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
170 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
171 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
173 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
175 // particletexture_t is a rectangle in the particlefonttexture
176 typedef struct particletexture_s
179 float s1, t1, s2, t2;
183 static rtexturepool_t *particletexturepool;
184 static rtexture_t *particlefonttexture;
185 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
186 skinframe_t *decalskinframe;
188 // texture numbers in particle font
189 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
190 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
191 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
192 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
193 static const int tex_rainsplash = 32;
194 static const int tex_particle = 63;
195 static const int tex_bubble = 62;
196 static const int tex_raindrop = 61;
197 static const int tex_beam = 60;
199 particleeffectinfo_t baselineparticleeffectinfo =
201 0, //int effectnameindex; // which effect this belongs to
202 // PARTICLEEFFECT_* bits
204 // blood effects may spawn very few particles, so proper fraction-overflow
205 // handling is very important, this variable keeps track of the fraction
206 0.0, //double particleaccumulator;
207 // the math is: countabsolute + requestedcount * countmultiplier * quality
208 // absolute number of particles to spawn, often used for decals
209 // (unaffected by quality and requestedcount)
210 0.0f, //float countabsolute;
211 // multiplier for the number of particles CL_ParticleEffect was told to
212 // spawn, most effects do not really have a count and hence use 1, so
213 // this is often the actual count to spawn, not merely a multiplier
214 0.0f, //float countmultiplier;
215 // if > 0 this causes the particle to spawn in an evenly spaced line from
216 // originmins to originmaxs (causing them to describe a trail, not a box)
217 0.0f, //float trailspacing;
218 // type of particle to spawn (defines some aspects of behavior)
219 pt_alphastatic, //ptype_t particletype;
220 // blending mode used on this particle type
221 PBLEND_ALPHA, //pblend_t blendmode;
222 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
223 PARTICLE_BILLBOARD, //porientation_t orientation;
224 // range of colors to choose from in hex RRGGBB (like HTML color tags),
225 // randomly interpolated at spawn
226 {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2];
227 // a random texture is chosen in this range (note the second value is one
228 // past the last choosable, so for example 8,16 chooses any from 8 up and
230 // if start and end of the range are the same, no randomization is done
231 {63, 63 /* tex_particle */}, //int tex[2];
232 // range of size values randomly chosen when spawning, plus size increase over time
233 {1, 1, 0.0f}, //float size[3];
234 // range of alpha values randomly chosen when spawning, plus alpha fade
235 {0.0f, 256.0f, 256.0f}, //float alpha[3];
236 // how long the particle should live (note it is also removed if alpha drops to 0)
237 {16777216.0f, 16777216.0f}, //float time[2];
238 // how much gravity affects this particle (negative makes it fly up!)
239 0.0f, //float gravity;
240 // how much bounce the particle has when it hits a surface
241 // if negative the particle is removed on impact
242 0.0f, //float bounce;
243 // if in air this friction is applied
244 // if negative the particle accelerates
245 0.0f, //float airfriction;
246 // if in liquid (water/slime/lava) this friction is applied
247 // if negative the particle accelerates
248 0.0f, //float liquidfriction;
249 // these offsets are added to the values given to particleeffect(), and
250 // then an ellipsoid-shaped jitter is added as defined by these
251 // (they are the 3 radii)
252 1.0f, //float stretchfactor;
253 // stretch velocity factor (used for sparks)
254 {0.0f, 0.0f, 0.0f}, //float originoffset[3];
255 {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3];
256 {0.0f, 0.0f, 0.0f}, //float velocityoffset[3];
257 {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3];
258 {0.0f, 0.0f, 0.0f}, //float originjitter[3];
259 {0.0f, 0.0f, 0.0f}, //float velocityjitter[3];
260 0.0f, //float velocitymultiplier;
261 // an effect can also spawn a dlight
262 0.0f, //float lightradiusstart;
263 0.0f, //float lightradiusfade;
264 16777216.0f, //float lighttime;
265 {1.0f, 1.0f, 1.0f}, //float lightcolor[3];
266 true, //qboolean lightshadow;
267 0, //int lightcubemapnum;
268 {1.0f, 0.25f}, //float lightcorona[2];
269 {(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!
270 {-1, -1}, //int staintex[2];
271 {1.0f, 1.0f}, //float stainalpha[2];
272 {2.0f, 2.0f}, //float stainsize[2];
274 {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time
277 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
278 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
279 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
280 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
281 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
282 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
283 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
284 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
285 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
286 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
287 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
288 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
289 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
290 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
291 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
292 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
293 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
294 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
295 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
296 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
297 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
298 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
299 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
300 cvar_t cl_particles_forcetraileffects = {0, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"};
301 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
302 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
303 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
304 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
305 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"};
306 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
307 cvar_t cl_decals_newsystem_immediatebloodstain = {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"};
308 cvar_t cl_decals_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"};
309 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
310 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
311 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
314 static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
320 particleeffectinfo_t *info = NULL;
321 const char *text = textstart;
323 for (linenumber = 1;;linenumber++)
326 for (arrayindex = 0;arrayindex < 16;arrayindex++)
327 argv[arrayindex][0] = 0;
330 if (!COM_ParseToken_Simple(&text, true, false, true))
332 if (!strcmp(com_token, "\n"))
336 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
342 #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;}
343 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
344 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
345 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
346 #define readfloat(var) checkparms(2);var = atof(argv[1])
347 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
348 if (!strcmp(argv[0], "effect"))
352 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
354 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
357 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
359 if (particleeffectname[effectnameindex][0])
361 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
366 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
370 // if we run out of names, abort
371 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
373 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
376 for(i = 0; i < numparticleeffectinfo; ++i)
378 info = particleeffectinfo + i;
379 if(!(info->flags & PARTICLEEFFECT_DEFINED))
380 if(info->effectnameindex == effectnameindex)
383 if(i < numparticleeffectinfo)
385 info = particleeffectinfo + numparticleeffectinfo++;
386 // copy entire info from baseline, then fix up the nameindex
387 *info = baselineparticleeffectinfo;
388 info->effectnameindex = effectnameindex;
391 else if (info == NULL)
393 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
397 info->flags |= PARTICLEEFFECT_DEFINED;
398 if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
399 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
400 else if (!strcmp(argv[0], "type"))
403 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
404 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
405 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
406 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
407 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
408 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
409 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
410 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
411 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
412 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
413 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
414 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
415 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
416 info->blendmode = particletype[info->particletype].blendmode;
417 info->orientation = particletype[info->particletype].orientation;
419 else if (!strcmp(argv[0], "blend"))
422 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
423 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
424 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
425 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
427 else if (!strcmp(argv[0], "orientation"))
430 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
431 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
432 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
433 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
434 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
436 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
437 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
438 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
439 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
440 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
441 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
442 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
443 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
444 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
445 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
446 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
447 else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);}
448 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
449 else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);}
450 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
451 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
452 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
453 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
454 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
455 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
456 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
457 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
458 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
459 else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);}
460 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
461 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
462 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
463 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
464 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
465 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
466 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
467 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
468 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; }
469 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
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(void)
576 CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL);
584 void CL_ReadPointFile_f (void);
585 void CL_Particles_Init (void)
587 Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)");
588 Cmd_AddCommand ("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_visculling);
616 Cvar_RegisterVariable (&cl_decals_time);
617 Cvar_RegisterVariable (&cl_decals_fadetime);
618 Cvar_RegisterVariable (&cl_decals_newsystem);
619 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
620 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
621 Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears);
622 Cvar_RegisterVariable (&cl_decals_models);
623 Cvar_RegisterVariable (&cl_decals_bias);
624 Cvar_RegisterVariable (&cl_decals_max);
627 void CL_Particles_Shutdown (void)
631 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
632 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
634 // list of all 26 parameters:
635 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
636 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
637 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
638 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
639 // palpha - opacity of particle as 0-255 (can be more than 255)
640 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
641 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
642 // pgravity - how much effect gravity has on the particle (0-1)
643 // 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
644 // px,py,pz - starting origin of particle
645 // pvx,pvy,pvz - starting velocity of particle
646 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
647 // blendmode - one of the PBLEND_ values
648 // orientation - one of the PARTICLE_ values
649 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
650 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
651 // stainalpha: opacity of the stain as factor for alpha
652 // stainsize: size of the stain as factor for palpha
653 // angle: base rotation of the particle geometry around its center normal
654 // spin: rotation speed of the particle geometry around its center normal
655 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])
660 if (!cl_particles.integer)
662 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
663 if (cl.free_particle >= cl.max_particles)
666 lifetime = palpha / min(1, palphafade);
667 part = &cl.particles[cl.free_particle++];
668 if (cl.num_particles < cl.free_particle)
669 cl.num_particles = cl.free_particle;
670 memset(part, 0, sizeof(*part));
671 VectorCopy(sortorigin, part->sortorigin);
672 part->typeindex = ptypeindex;
673 part->blendmode = blendmode;
674 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
676 particletexture_t *tex = &particletexture[ptex];
677 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
678 part->orientation = PARTICLE_VBEAM;
680 part->orientation = PARTICLE_HBEAM;
683 part->orientation = orientation;
684 l2 = (int)lhrandom(0.5, 256.5);
686 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
687 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
688 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
691 part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f);
692 part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f);
693 part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f);
695 part->alpha = palpha;
696 part->alphafade = palphafade;
697 part->staintexnum = staintex;
698 if(staincolor1 >= 0 && staincolor2 >= 0)
700 l2 = (int)lhrandom(0.5, 256.5);
702 if(blendmode == PBLEND_INVMOD)
704 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
705 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
706 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
710 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
711 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
712 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
714 if(r > 0xFF) r = 0xFF;
715 if(g > 0xFF) g = 0xFF;
716 if(b > 0xFF) b = 0xFF;
720 r = part->color[0]; // -1 is shorthand for stain = particle color
724 part->staincolor[0] = r;
725 part->staincolor[1] = g;
726 part->staincolor[2] = b;
727 part->stainalpha = palpha * stainalpha;
728 part->stainsize = psize * stainsize;
731 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
733 part->color[0] *= tint[0];
734 part->color[1] *= tint[1];
735 part->color[2] *= tint[2];
737 part->alpha *= tint[3];
738 part->alphafade *= tint[3];
739 part->stainalpha *= tint[3];
743 part->sizeincrease = psizeincrease;
744 part->gravity = pgravity;
745 part->bounce = pbounce;
746 part->stretch = stretch;
748 part->org[0] = px + originjitter * v[0];
749 part->org[1] = py + originjitter * v[1];
750 part->org[2] = pz + originjitter * v[2];
751 part->vel[0] = pvx + velocityjitter * v[0];
752 part->vel[1] = pvy + velocityjitter * v[1];
753 part->vel[2] = pvz + velocityjitter * v[2];
755 part->airfriction = pairfriction;
756 part->liquidfriction = pliquidfriction;
757 part->die = cl.time + lifetime;
758 part->delayedspawn = cl.time;
759 // part->delayedcollisions = 0;
760 part->qualityreduction = pqualityreduction;
763 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
764 if (part->typeindex == pt_rain)
768 float lifetime = part->die - cl.time;
771 // turn raindrop into simple spark and create delayedspawn splash effect
772 part->typeindex = pt_spark;
774 VectorMA(part->org, lifetime, part->vel, endvec);
775 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, collision_extendmovelength.value, true, false, NULL, false, false);
776 part->die = cl.time + lifetime * trace.fraction;
777 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);
780 part2->delayedspawn = part->die;
781 part2->die += part->die - cl.time;
782 for (i = rand() & 7;i < 10;i++)
784 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);
787 part2->delayedspawn = part->die;
788 part2->die += part->die - cl.time;
794 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
796 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
799 VectorMA(part->org, lifetime, part->vel, endvec);
800 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
801 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
808 static void CL_ImmediateBloodStain(particle_t *part)
813 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
814 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
816 VectorCopy(part->vel, v);
818 staintex = part->staintexnum;
819 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);
822 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
823 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
825 VectorCopy(part->vel, v);
827 staintex = tex_blooddecal[rand()&7];
828 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);
832 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
836 entity_render_t *ent = &cl.entities[hitent].render;
837 unsigned char color[3];
838 if (!cl_decals.integer)
840 if (!ent->allowdecals)
843 l2 = (int)lhrandom(0.5, 256.5);
845 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
846 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
847 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
849 if (cl_decals_newsystem.integer)
852 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);
854 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);
858 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
859 if (cl.free_decal >= cl.max_decals)
861 decal = &cl.decals[cl.free_decal++];
862 if (cl.num_decals < cl.free_decal)
863 cl.num_decals = cl.free_decal;
864 memset(decal, 0, sizeof(*decal));
865 decal->decalsequence = cl.decalsequence++;
866 decal->typeindex = pt_decal;
867 decal->texnum = texnum;
868 VectorMA(org, cl_decals_bias.value, normal, decal->org);
869 VectorCopy(normal, decal->normal);
871 decal->alpha = alpha;
872 decal->time2 = cl.time;
873 decal->color[0] = color[0];
874 decal->color[1] = color[1];
875 decal->color[2] = color[2];
878 decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f);
879 decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f);
880 decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f);
882 decal->owner = hitent;
883 decal->clusterindex = -1000; // no vis culling unless we're sure
886 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
887 decal->ownermodel = cl.entities[decal->owner].render.model;
888 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
889 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
893 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
895 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
897 decal->clusterindex = leaf->clusterindex;
902 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
909 int besthitent = 0, hitent;
912 for (i = 0;i < 32;i++)
915 VectorMA(org, maxdist, org2, org2);
916 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, &hitent, false, true);
917 // take the closest trace result that doesn't end up hitting a NOMARKS
918 // surface (sky for example)
919 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
921 bestfrac = trace.fraction;
923 VectorCopy(trace.endpos, bestorg);
924 VectorCopy(trace.plane.normal, bestnormal);
928 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
931 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
932 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
933 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);
934 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)
937 matrix4x4_t tempmatrix;
940 VectorLerp(originmins, 0.5, originmaxs, center);
941 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
942 if (effectnameindex == EFFECT_SVC_PARTICLE)
944 if (cl_particles.integer)
946 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
948 CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
949 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
950 CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
953 count *= cl_particles_quality.value;
954 for (;count > 0;count--)
956 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
957 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);
962 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
963 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
964 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
965 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
966 else if (effectnameindex == EFFECT_TE_SPIKE)
968 if (cl_particles_bulletimpacts.integer)
970 if (cl_particles_quake.integer)
972 if (cl_particles_smoke.integer)
973 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
977 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
978 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
979 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);
983 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
984 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
986 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
988 if (cl_particles_bulletimpacts.integer)
990 if (cl_particles_quake.integer)
992 if (cl_particles_smoke.integer)
993 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
997 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
998 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
999 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);
1003 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1004 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1005 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1007 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
1009 if (cl_particles_bulletimpacts.integer)
1011 if (cl_particles_quake.integer)
1013 if (cl_particles_smoke.integer)
1014 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1018 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1019 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1020 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);
1024 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1025 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1027 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
1029 if (cl_particles_bulletimpacts.integer)
1031 if (cl_particles_quake.integer)
1033 if (cl_particles_smoke.integer)
1034 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1038 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
1039 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
1040 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);
1044 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1045 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1046 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1048 else if (effectnameindex == EFFECT_TE_BLOOD)
1050 if (!cl_particles_blood.integer)
1052 if (cl_particles_quake.integer)
1053 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1056 static double bloodaccumulator = 0;
1057 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
1058 //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);
1059 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
1060 for (;bloodaccumulator > 0;bloodaccumulator--)
1062 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);
1063 if (immediatebloodstain && part)
1065 immediatebloodstain = false;
1066 CL_ImmediateBloodStain(part);
1071 else if (effectnameindex == EFFECT_TE_SPARK)
1072 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
1073 else if (effectnameindex == EFFECT_TE_PLASMABURN)
1075 // plasma scorch mark
1076 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1077 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1078 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1080 else if (effectnameindex == EFFECT_TE_GUNSHOT)
1082 if (cl_particles_bulletimpacts.integer)
1084 if (cl_particles_quake.integer)
1085 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1088 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1089 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1090 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);
1094 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1095 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1097 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
1099 if (cl_particles_bulletimpacts.integer)
1101 if (cl_particles_quake.integer)
1102 CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail);
1105 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1106 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1107 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);
1111 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1112 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1113 CL_AllocLightFlash(NULL, &tempmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1115 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1117 CL_ParticleExplosion(center);
1118 CL_AllocLightFlash(NULL, &tempmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1120 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1122 CL_ParticleExplosion(center);
1123 CL_AllocLightFlash(NULL, &tempmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1125 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1127 if (cl_particles_quake.integer)
1130 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1133 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);
1135 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);
1139 CL_ParticleExplosion(center);
1140 CL_AllocLightFlash(NULL, &tempmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1142 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1143 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1144 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1146 count *= cl_particles_quality.value;
1148 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);
1150 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1152 float i, j, inc, vel;
1155 inc = 8 / cl_particles_quality.value;
1156 for (i = -128;i < 128;i += inc)
1158 for (j = -128;j < 128;j += inc)
1160 dir[0] = j + lhrandom(0, inc);
1161 dir[1] = i + lhrandom(0, inc);
1163 org[0] = center[0] + dir[0];
1164 org[1] = center[1] + dir[1];
1165 org[2] = center[2] + lhrandom(0, 64);
1166 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1167 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);
1171 else if (effectnameindex == EFFECT_TE_TELEPORT)
1173 float i, j, k, inc, vel;
1176 if (cl_particles_quake.integer)
1177 inc = 4 / cl_particles_quality.value;
1179 inc = 8 / cl_particles_quality.value;
1180 for (i = -16;i < 16;i += inc)
1182 for (j = -16;j < 16;j += inc)
1184 for (k = -24;k < 32;k += inc)
1186 VectorSet(dir, i*8, j*8, k*8);
1187 VectorNormalize(dir);
1188 vel = lhrandom(50, 113);
1189 if (cl_particles_quake.integer)
1190 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);
1192 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);
1196 if (!cl_particles_quake.integer)
1197 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);
1198 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1200 else if (effectnameindex == EFFECT_TE_TEI_G3)
1201 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);
1202 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1204 if (cl_particles_smoke.integer)
1206 count *= 0.25f * cl_particles_quality.value;
1208 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);
1211 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1213 CL_ParticleExplosion(center);
1214 CL_AllocLightFlash(NULL, &tempmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1216 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1219 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1220 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1221 if (cl_particles_smoke.integer)
1222 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1223 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);
1224 if (cl_particles_sparks.integer)
1225 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1226 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);
1227 CL_AllocLightFlash(NULL, &tempmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1229 else if (effectnameindex == EFFECT_EF_FLAME)
1231 if (!spawnparticles)
1233 count *= 300 * cl_particles_quality.value;
1235 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);
1236 CL_AllocLightFlash(NULL, &tempmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1238 else if (effectnameindex == EFFECT_EF_STARDUST)
1240 if (!spawnparticles)
1242 count *= 200 * cl_particles_quality.value;
1244 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);
1245 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1247 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1251 int smoke, blood, bubbles, r, color;
1253 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1256 Vector4Set(light, 0, 0, 0, 0);
1258 if (effectnameindex == EFFECT_TR_ROCKET)
1259 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1260 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1262 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1263 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1265 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1267 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1268 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1272 matrix4x4_t tempmatrix;
1273 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1274 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1275 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1279 if (!spawnparticles)
1282 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1285 VectorSubtract(originmaxs, originmins, dir);
1286 len = VectorNormalizeLength(dir);
1289 dec = -ent->persistent.trail_time;
1290 ent->persistent.trail_time += len;
1291 if (ent->persistent.trail_time < 0.01f)
1294 // if we skip out, leave it reset
1295 ent->persistent.trail_time = 0.0f;
1300 // advance into this frame to reach the first puff location
1301 VectorMA(originmins, dec, dir, pos);
1304 smoke = cl_particles.integer && cl_particles_smoke.integer;
1305 blood = cl_particles.integer && cl_particles_blood.integer;
1306 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1307 qd = 1.0f / cl_particles_quality.value;
1314 if (effectnameindex == EFFECT_TR_BLOOD)
1316 if (cl_particles_quake.integer)
1318 color = particlepalette[67 + (rand()&3)];
1319 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);
1324 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);
1327 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1329 if (cl_particles_quake.integer)
1332 color = particlepalette[67 + (rand()&3)];
1333 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);
1338 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);
1344 if (effectnameindex == EFFECT_TR_ROCKET)
1346 if (cl_particles_quake.integer)
1349 color = particlepalette[ramp3[r]];
1350 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);
1354 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);
1355 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);
1358 else if (effectnameindex == EFFECT_TR_GRENADE)
1360 if (cl_particles_quake.integer)
1363 color = particlepalette[ramp3[r]];
1364 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);
1368 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);
1371 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1373 if (cl_particles_quake.integer)
1376 color = particlepalette[52 + (rand()&7)];
1377 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);
1378 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);
1380 else if (gamemode == GAME_GOODVSBAD2)
1383 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);
1387 color = particlepalette[20 + (rand()&7)];
1388 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);
1391 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1393 if (cl_particles_quake.integer)
1396 color = particlepalette[230 + (rand()&7)];
1397 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);
1398 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);
1402 color = particlepalette[226 + (rand()&7)];
1403 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);
1406 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1408 if (cl_particles_quake.integer)
1410 color = particlepalette[152 + (rand()&3)];
1411 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);
1413 else if (gamemode == GAME_GOODVSBAD2)
1416 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);
1418 else if (gamemode == GAME_PRYDON)
1421 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);
1424 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);
1426 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1429 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);
1431 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1434 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);
1436 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1437 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);
1441 if (effectnameindex == EFFECT_TR_ROCKET)
1442 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);
1443 else if (effectnameindex == EFFECT_TR_GRENADE)
1444 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);
1446 // advance to next time and position
1449 VectorMA (pos, dec, dir, pos);
1452 ent->persistent.trail_time = len;
1455 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1458 // this is also called on point effects with spawndlight = true and
1459 // spawnparticles = true
1460 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)
1462 qboolean found = false;
1464 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1466 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1467 return; // no such effect
1469 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1471 int effectinfoindex;
1474 particleeffectinfo_t *info;
1486 qboolean underwater;
1487 qboolean immediatebloodstain;
1489 float avgtint[4], tint[4], tintlerp;
1490 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1491 VectorLerp(originmins, 0.5, originmaxs, center);
1492 supercontents = CL_PointSuperContents(center);
1493 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1494 VectorSubtract(originmaxs, originmins, traildir);
1495 traillen = VectorLength(traildir);
1496 VectorNormalize(traildir);
1499 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1503 Vector4Set(avgtint, 1, 1, 1, 1);
1505 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1507 if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED))
1509 qboolean definedastrail = info->trailspacing > 0;
1511 qboolean drawastrail = wanttrail;
1512 if (cl_particles_forcetraileffects.integer)
1513 drawastrail = drawastrail || definedastrail;
1516 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1518 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1521 // spawn a dlight if requested
1522 if (info->lightradiusstart > 0 && spawndlight)
1524 matrix4x4_t tempmatrix;
1526 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1528 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1529 if (info->lighttime > 0 && info->lightradiusfade > 0)
1531 // light flash (explosion, etc)
1532 // called when effect starts
1533 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, info->lightcubemapnum, -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1535 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1538 // called by CL_LinkNetworkEntity
1539 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1540 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1541 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1542 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1543 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1544 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1548 if (!spawnparticles)
1553 if (info->tex[1] > info->tex[0])
1555 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1556 tex = min(tex, info->tex[1] - 1);
1558 if(info->staintex[0] < 0)
1559 staintex = info->staintex[0];
1562 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1563 staintex = min(staintex, info->staintex[1] - 1);
1565 if (info->particletype == pt_decal)
1567 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1568 AnglesFromVectors(angles, velocity, NULL, false);
1569 AngleVectors(angles, forward, right, up);
1570 VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1572 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]);
1574 else if (info->orientation == PARTICLE_HBEAM)
1579 AnglesFromVectors(angles, traildir, NULL, false);
1580 AngleVectors(angles, forward, right, up);
1581 VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1583 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);
1588 if (!cl_particles.integer)
1590 switch (info->particletype)
1592 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1593 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1594 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1595 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1596 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1597 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1601 cnt = info->countabsolute;
1602 cnt += (pcount * info->countmultiplier) * cl_particles_quality.value;
1603 // if drawastrail is not set, we will
1604 // use the regular cnt-based random
1605 // particle spawning at the center; so
1606 // do NOT apply trailspacing then!
1607 if (drawastrail && definedastrail)
1608 cnt += (traillen / info->trailspacing) * cl_particles_quality.value;
1611 continue; // nothing to draw
1612 info->particleaccumulator += cnt;
1614 if (drawastrail || definedastrail)
1615 immediatebloodstain = false;
1617 immediatebloodstain =
1618 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1620 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1624 VectorCopy(originmins, trailpos);
1625 trailstep = traillen / cnt;
1629 VectorCopy(center, trailpos);
1635 VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity);
1636 AnglesFromVectors(angles, velocity, NULL, false);
1639 AnglesFromVectors(angles, traildir, NULL, false);
1641 AngleVectors(angles, forward, right, up);
1642 VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos);
1643 VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity);
1644 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1645 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1647 if (info->tex[1] > info->tex[0])
1649 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1650 tex = min(tex, info->tex[1] - 1);
1652 if (!(drawastrail || definedastrail))
1654 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1655 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1656 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1660 tintlerp = lhrandom(0, 1);
1661 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1664 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);
1665 if (immediatebloodstain && part)
1667 immediatebloodstain = false;
1668 CL_ImmediateBloodStain(part);
1671 VectorMA(trailpos, trailstep, traildir, trailpos);
1678 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail);
1681 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)
1683 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true);
1686 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)
1688 CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false);
1691 // note: this one ONLY does boxes!
1692 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)
1694 CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1);
1702 void CL_EntityParticles (const entity_t *ent)
1705 vec_t pitch, yaw, dist = 64, beamlength = 16;
1707 static vec3_t avelocities[NUMVERTEXNORMALS];
1708 if (!cl_particles.integer) return;
1709 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1711 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1713 if (!avelocities[0][0])
1714 for (i = 0;i < NUMVERTEXNORMALS;i++)
1715 for (j = 0;j < 3;j++)
1716 avelocities[i][j] = lhrandom(0, 2.55);
1718 for (i = 0;i < NUMVERTEXNORMALS;i++)
1720 yaw = cl.time * avelocities[i][0];
1721 pitch = cl.time * avelocities[i][1];
1722 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1723 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1724 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1725 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);
1730 void CL_ReadPointFile_f (void)
1732 double org[3], leakorg[3];
1735 char *pointfile = NULL, *pointfilepos, *t, tchar;
1736 char name[MAX_QPATH];
1741 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1742 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1745 Con_Printf("Could not open %s\n", name);
1749 Con_Printf("Reading %s...\n", name);
1750 VectorClear(leakorg);
1753 pointfilepos = pointfile;
1754 while (*pointfilepos)
1756 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1761 while (*t && *t != '\n' && *t != '\r')
1765 #if _MSC_VER >= 1400
1766 #define sscanf sscanf_s
1768 r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]);
1769 VectorCopy(org, vecorg);
1775 VectorCopy(org, leakorg);
1778 if (cl.num_particles < cl.max_particles - 3)
1781 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);
1784 Mem_Free(pointfile);
1785 VectorCopy(leakorg, vecorg);
1786 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]);
1793 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);
1794 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);
1795 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);
1800 CL_ParseParticleEffect
1802 Parse an effect out of the server message
1805 void CL_ParseParticleEffect (void)
1808 int i, count, msgcount, color;
1810 MSG_ReadVector(&cl_message, org, cls.protocol);
1811 for (i=0 ; i<3 ; i++)
1812 dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0);
1813 msgcount = MSG_ReadByte(&cl_message);
1814 color = MSG_ReadByte(&cl_message);
1816 if (msgcount == 255)
1821 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1826 CL_ParticleExplosion
1830 void CL_ParticleExplosion (const vec3_t org)
1836 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1837 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1839 if (cl_particles_quake.integer)
1841 for (i = 0;i < 1024;i++)
1847 color = particlepalette[ramp1[r]];
1848 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);
1852 color = particlepalette[ramp2[r]];
1853 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);
1859 i = CL_PointSuperContents(org);
1860 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1862 if (cl_particles.integer && cl_particles_bubbles.integer)
1863 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1864 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);
1868 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1870 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1877 VectorMA(org, 128, v2, v);
1878 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, collision_extendmovelength.value, true, false, NULL, false, false);
1880 while (k < 16 && trace.fraction < 0.1f);
1881 VectorSubtract(trace.endpos, org, v2);
1882 VectorScale(v2, 2.0f, v2);
1883 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);
1889 if (cl_particles_explosions_shell.integer)
1890 R_NewExplosion(org);
1895 CL_ParticleExplosion2
1899 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1902 if (!cl_particles.integer) return;
1904 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1906 k = particlepalette[colorStart + (i % colorLength)];
1907 if (cl_particles_quake.integer)
1908 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);
1910 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);
1914 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1917 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1918 if (cl_particles_sparks.integer)
1920 sparkcount *= cl_particles_quality.value;
1921 while(sparkcount-- > 0)
1922 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);
1926 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1929 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1930 if (cl_particles_smoke.integer)
1932 smokecount *= cl_particles_quality.value;
1933 while(smokecount-- > 0)
1934 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);
1938 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)
1942 if (!cl_particles.integer) return;
1943 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1945 count = (int)(count * cl_particles_quality.value);
1948 k = particlepalette[colorbase + (rand()&3)];
1949 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);
1953 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1956 float minz, maxz, lifetime = 30;
1958 if (!cl_particles.integer) return;
1959 if (dir[2] < 0) // falling
1961 minz = maxs[2] + dir[2] * 0.1;
1964 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1969 maxz = maxs[2] + dir[2] * 0.1;
1971 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1974 count = (int)(count * cl_particles_quality.value);
1979 if (!cl_particles_rain.integer) break;
1980 count *= 4; // ick, this should be in the mod or maps?
1984 k = particlepalette[colorbase + (rand()&3)];
1985 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1986 if (gamemode == GAME_GOODVSBAD2)
1987 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);
1989 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);
1993 if (!cl_particles_snow.integer) break;
1996 k = particlepalette[colorbase + (rand()&3)];
1997 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1998 if (gamemode == GAME_GOODVSBAD2)
1999 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);
2001 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);
2005 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
2009 cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
2010 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
2011 static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"};
2012 static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"};
2013 cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
2014 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
2016 #define PARTICLETEXTURESIZE 64
2017 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
2019 static unsigned char shadebubble(float dx, float dy, vec3_t light)
2023 dz = 1 - (dx*dx+dy*dy);
2024 if (dz > 0) // it does hit the sphere
2028 normal[0] = dx;normal[1] = dy;normal[2] = dz;
2029 VectorNormalize(normal);
2030 dot = DotProduct(normal, light);
2031 if (dot > 0.5) // interior reflection
2032 f += ((dot * 2) - 1);
2033 else if (dot < -0.5) // exterior reflection
2034 f += ((dot * -2) - 1);
2036 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
2037 VectorNormalize(normal);
2038 dot = DotProduct(normal, light);
2039 if (dot > 0.5) // interior reflection
2040 f += ((dot * 2) - 1);
2041 else if (dot < -0.5) // exterior reflection
2042 f += ((dot * -2) - 1);
2044 f += 16; // just to give it a haze so you can see the outline
2045 f = bound(0, f, 255);
2046 return (unsigned char) f;
2052 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
2053 static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
2055 *basex = (texnum % particlefontcols) * particlefontcellwidth;
2056 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
2057 *width = particlefontcellwidth;
2058 *height = particlefontcellheight;
2061 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
2063 int basex, basey, w, h, y;
2064 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
2065 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
2066 Sys_Error("invalid particle texture size for autogenerating");
2067 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2068 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
2071 static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
2074 float cx, cy, dx, dy, f, iradius;
2076 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2077 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
2078 iradius = 1.0f / radius;
2079 alpha *= (1.0f / 255.0f);
2080 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2082 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2086 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
2091 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
2092 d[0] += (int)(f * (blue - d[0]));
2093 d[1] += (int)(f * (green - d[1]));
2094 d[2] += (int)(f * (red - d[2]));
2101 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
2104 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2106 data[0] = bound(minb, data[0], maxb);
2107 data[1] = bound(ming, data[1], maxg);
2108 data[2] = bound(minr, data[2], maxr);
2113 static void particletextureinvert(unsigned char *data)
2116 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
2118 data[0] = 255 - data[0];
2119 data[1] = 255 - data[1];
2120 data[2] = 255 - data[2];
2124 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
2125 static void R_InitBloodTextures (unsigned char *particletexturedata)
2128 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2129 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2132 for (i = 0;i < 8;i++)
2134 memset(data, 255, datasize);
2135 for (k = 0;k < 24;k++)
2136 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
2137 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2138 particletextureinvert(data);
2139 setuptex(tex_bloodparticle[i], data, particletexturedata);
2143 for (i = 0;i < 8;i++)
2145 memset(data, 255, datasize);
2147 for (j = 1;j < 10;j++)
2148 for (k = min(j, m - 1);k < m;k++)
2149 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
2150 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
2151 particletextureinvert(data);
2152 setuptex(tex_blooddecal[i], data, particletexturedata);
2158 //uncomment this to make engine save out particle font to a tga file when run
2159 //#define DUMPPARTICLEFONT
2161 static void R_InitParticleTexture (void)
2163 int x, y, d, i, k, m;
2164 int basex, basey, w, h;
2165 float dx, dy, f, s1, t1, s2, t2;
2168 fs_offset_t filesize;
2169 char texturename[MAX_QPATH];
2172 // a note: decals need to modulate (multiply) the background color to
2173 // properly darken it (stain), and they need to be able to alpha fade,
2174 // this is a very difficult challenge because it means fading to white
2175 // (no change to background) rather than black (darkening everything
2176 // behind the whole decal polygon), and to accomplish this the texture is
2177 // inverted (dark red blood on white background becomes brilliant cyan
2178 // and white on black background) so we can alpha fade it to black, then
2179 // we invert it again during the blendfunc to make it work...
2181 #ifndef DUMPPARTICLEFONT
2182 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false);
2185 particlefonttexture = decalskinframe->base;
2186 // TODO maybe allow custom grid size?
2187 particlefontwidth = image_width;
2188 particlefontheight = image_height;
2189 particlefontcellwidth = image_width / 8;
2190 particlefontcellheight = image_height / 8;
2191 particlefontcols = 8;
2192 particlefontrows = 8;
2197 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2198 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2199 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2200 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2201 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2203 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2204 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2205 particlefontcols = 8;
2206 particlefontrows = 8;
2208 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2211 for (i = 0;i < 8;i++)
2213 memset(data, 255, datasize);
2216 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2217 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2219 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2221 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2222 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2224 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2225 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2227 d = (int)(d * (1-(dx*dx+dy*dy)));
2228 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2229 d = bound(0, d, 255);
2230 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2237 setuptex(tex_smoke[i], data, particletexturedata);
2241 memset(data, 255, datasize);
2242 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2244 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2245 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2247 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2248 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2249 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2252 setuptex(tex_rainsplash, data, particletexturedata);
2255 memset(data, 255, datasize);
2256 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2258 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2259 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2261 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2262 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2263 d = bound(0, d, 255);
2264 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2267 setuptex(tex_particle, data, particletexturedata);
2270 memset(data, 255, datasize);
2271 light[0] = 1;light[1] = 1;light[2] = 1;
2272 VectorNormalize(light);
2273 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2275 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2276 // stretch upper half of bubble by +50% and shrink lower half by -50%
2277 // (this gives an elongated teardrop shape)
2279 dy = (dy - 0.5f) * 2.0f;
2281 dy = (dy - 0.5f) / 1.5f;
2282 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2284 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2285 // shrink bubble width to half
2287 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2290 setuptex(tex_raindrop, data, particletexturedata);
2293 memset(data, 255, datasize);
2294 light[0] = 1;light[1] = 1;light[2] = 1;
2295 VectorNormalize(light);
2296 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2298 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2299 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2301 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2302 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2305 setuptex(tex_bubble, data, particletexturedata);
2307 // Blood particles and blood decals
2308 R_InitBloodTextures (particletexturedata);
2311 for (i = 0;i < 8;i++)
2313 memset(data, 255, datasize);
2314 for (k = 0;k < 12;k++)
2315 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2316 for (k = 0;k < 3;k++)
2317 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2318 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2319 particletextureinvert(data);
2320 setuptex(tex_bulletdecal[i], data, particletexturedata);
2323 #ifdef DUMPPARTICLEFONT
2324 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2327 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false);
2328 particlefonttexture = decalskinframe->base;
2330 Mem_Free(particletexturedata);
2335 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2337 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2338 particletexture[i].texture = particlefonttexture;
2339 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2340 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2341 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2342 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2345 #ifndef DUMPPARTICLEFONT
2346 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D);
2347 if (!particletexture[tex_beam].texture)
2350 unsigned char noise3[64][64], data2[64][16][4];
2352 fractalnoise(&noise3[0][0], 64, 4);
2354 for (y = 0;y < 64;y++)
2356 dy = (y - 0.5f*64) / (64*0.5f-1);
2357 for (x = 0;x < 16;x++)
2359 dx = (x - 0.5f*16) / (16*0.5f-2);
2360 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2361 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2362 data2[y][x][3] = 255;
2366 #ifdef DUMPPARTICLEFONT
2367 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2369 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL);
2371 particletexture[tex_beam].s1 = 0;
2372 particletexture[tex_beam].t1 = 0;
2373 particletexture[tex_beam].s2 = 1;
2374 particletexture[tex_beam].t2 = 1;
2376 // now load an texcoord/texture override file
2377 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2384 if(!COM_ParseToken_Simple(&bufptr, true, false, true))
2386 if(!strcmp(com_token, "\n"))
2387 continue; // empty line
2388 i = atoi(com_token);
2396 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2398 strlcpy(texturename, com_token, sizeof(texturename));
2399 s1 = atof(com_token);
2400 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2403 t1 = atof(com_token);
2404 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2406 s2 = atof(com_token);
2407 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2409 t2 = atof(com_token);
2410 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2411 if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n"))
2412 strlcpy(texturename, com_token, sizeof(texturename));
2419 if (!texturename[0])
2421 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2424 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2426 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2429 sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active!
2432 // R_SkinFrame_LoadExternal already complained
2435 particletexture[i].texture = sf->base;
2436 particletexture[i].s1 = s1;
2437 particletexture[i].t1 = t1;
2438 particletexture[i].s2 = s2;
2439 particletexture[i].t2 = t2;
2445 static void r_part_start(void)
2448 // generate particlepalette for convenience from the main one
2449 for (i = 0;i < 256;i++)
2450 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2451 particletexturepool = R_AllocTexturePool();
2452 R_InitParticleTexture ();
2453 CL_Particles_LoadEffectInfo(NULL);
2456 static void r_part_shutdown(void)
2458 R_FreeTexturePool(&particletexturepool);
2461 static void r_part_newmap(void)
2464 R_SkinFrame_MarkUsed(decalskinframe);
2465 CL_Particles_LoadEffectInfo(NULL);
2468 unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6];
2469 float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16];
2471 void R_Particles_Init (void)
2474 for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++)
2476 particle_elements[i*6+0] = i*4+0;
2477 particle_elements[i*6+1] = i*4+1;
2478 particle_elements[i*6+2] = i*4+2;
2479 particle_elements[i*6+3] = i*4+0;
2480 particle_elements[i*6+4] = i*4+2;
2481 particle_elements[i*6+5] = i*4+3;
2484 Cvar_RegisterVariable(&r_drawparticles);
2485 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2486 Cvar_RegisterVariable(&r_drawparticles_nearclip_min);
2487 Cvar_RegisterVariable(&r_drawparticles_nearclip_max);
2488 Cvar_RegisterVariable(&r_drawdecals);
2489 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2490 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL);
2493 static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2495 int surfacelistindex;
2497 float *v3f, *t2f, *c4f;
2498 particletexture_t *tex;
2499 vec_t right[3], up[3], size, ca;
2500 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2502 RSurf_ActiveWorldEntity();
2504 r_refdef.stats[r_stat_drawndecals] += numsurfaces;
2505 // R_Mesh_ResetTextureState();
2506 GL_DepthMask(false);
2507 GL_DepthRange(0, 1);
2508 GL_PolygonOffset(0, 0);
2510 GL_CullFace(GL_NONE);
2512 // generate all the vertices at once
2513 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2515 d = cl.decals + surfacelist[surfacelistindex];
2518 c4f = particle_color4f + 16*surfacelistindex;
2519 ca = d->alpha * alphascale;
2520 // ensure alpha multiplier saturates properly
2521 if (ca > 1.0f / 256.0f)
2523 if (r_refdef.fogenabled)
2524 ca *= RSurf_FogVertex(d->org);
2525 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2526 Vector4Copy(c4f, c4f + 4);
2527 Vector4Copy(c4f, c4f + 8);
2528 Vector4Copy(c4f, c4f + 12);
2530 // calculate vertex positions
2531 size = d->size * cl_particles_size.value;
2532 VectorVectors(d->normal, right, up);
2533 VectorScale(right, size, right);
2534 VectorScale(up, size, up);
2535 v3f = particle_vertex3f + 12*surfacelistindex;
2536 v3f[ 0] = d->org[0] - right[0] - up[0];
2537 v3f[ 1] = d->org[1] - right[1] - up[1];
2538 v3f[ 2] = d->org[2] - right[2] - up[2];
2539 v3f[ 3] = d->org[0] - right[0] + up[0];
2540 v3f[ 4] = d->org[1] - right[1] + up[1];
2541 v3f[ 5] = d->org[2] - right[2] + up[2];
2542 v3f[ 6] = d->org[0] + right[0] + up[0];
2543 v3f[ 7] = d->org[1] + right[1] + up[1];
2544 v3f[ 8] = d->org[2] + right[2] + up[2];
2545 v3f[ 9] = d->org[0] + right[0] - up[0];
2546 v3f[10] = d->org[1] + right[1] - up[1];
2547 v3f[11] = d->org[2] + right[2] - up[2];
2549 // calculate texcoords
2550 tex = &particletexture[d->texnum];
2551 t2f = particle_texcoord2f + 8*surfacelistindex;
2552 t2f[0] = tex->s1;t2f[1] = tex->t2;
2553 t2f[2] = tex->s1;t2f[3] = tex->t1;
2554 t2f[4] = tex->s2;t2f[5] = tex->t1;
2555 t2f[6] = tex->s2;t2f[7] = tex->t2;
2558 // now render the decals all at once
2559 // (this assumes they all use one particle font texture!)
2560 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2561 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true);
2562 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2563 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2566 void R_DrawDecals (void)
2569 int drawdecals = r_drawdecals.integer;
2574 unsigned int killsequence = cl.decalsequence - bound(0, (unsigned int) cl_decals_max.integer, cl.decalsequence);
2576 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2577 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2579 // LordHavoc: early out conditions
2583 decalfade = frametime * 256 / cl_decals_fadetime.value;
2584 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2585 drawdist2 = drawdist2*drawdist2;
2587 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2589 if (!decal->typeindex)
2592 if (killsequence > decal->decalsequence)
2595 if (cl.time > decal->time2 + cl_decals_time.value)
2597 decal->alpha -= decalfade;
2598 if (decal->alpha <= 0)
2604 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2606 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2607 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2613 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2619 if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size))
2620 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2623 decal->typeindex = 0;
2624 if (cl.free_decal > i)
2628 // reduce cl.num_decals if possible
2629 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2632 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2634 decal_t *olddecals = cl.decals;
2635 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2636 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2637 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2638 Mem_Free(olddecals);
2641 r_refdef.stats[r_stat_totaldecals] = cl.num_decals;
2644 static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2646 vec3_t vecorg, vecvel, baseright, baseup;
2647 int surfacelistindex;
2648 int batchstart, batchcount;
2649 const particle_t *p;
2651 rtexture_t *texture;
2652 float *v3f, *t2f, *c4f;
2653 particletexture_t *tex;
2654 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2655 // float ambient[3], diffuse[3], diffusenormal[3];
2656 float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4;
2657 vec4_t colormultiplier;
2658 float minparticledist_start, minparticledist_end;
2661 RSurf_ActiveWorldEntity();
2663 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));
2665 r_refdef.stats[r_stat_particles] += numsurfaces;
2666 // R_Mesh_ResetTextureState();
2667 GL_DepthMask(false);
2668 GL_DepthRange(0, 1);
2669 GL_PolygonOffset(0, 0);
2671 GL_CullFace(GL_NONE);
2673 spintime = r_refdef.scene.time;
2675 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2676 minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value;
2677 dofade = (minparticledist_start < minparticledist_end);
2679 // first generate all the vertices at once
2680 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2682 p = cl.particles + surfacelist[surfacelistindex];
2684 blendmode = (pblend_t)p->blendmode;
2686 if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM)
2687 palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start));
2688 alpha = palpha * colormultiplier[3];
2689 // ensure alpha multiplier saturates properly
2695 case PBLEND_INVALID:
2697 // additive and modulate can just fade out in fog (this is correct)
2698 if (r_refdef.fogenabled)
2699 alpha *= RSurf_FogVertex(p->org);
2700 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2701 alpha *= 1.0f / 256.0f;
2702 c4f[0] = p->color[0] * alpha;
2703 c4f[1] = p->color[1] * alpha;
2704 c4f[2] = p->color[2] * alpha;
2708 // additive and modulate can just fade out in fog (this is correct)
2709 if (r_refdef.fogenabled)
2710 alpha *= RSurf_FogVertex(p->org);
2711 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2712 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2713 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2714 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2718 c4f[0] = p->color[0] * colormultiplier[0];
2719 c4f[1] = p->color[1] * colormultiplier[1];
2720 c4f[2] = p->color[2] * colormultiplier[2];
2722 // note: lighting is not cheap!
2723 if (particletype[p->typeindex].lighting)
2725 vecorg[0] = p->org[0];
2726 vecorg[1] = p->org[1];
2727 vecorg[2] = p->org[2];
2728 R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT);
2730 // mix in the fog color
2731 if (r_refdef.fogenabled)
2733 fog = RSurf_FogVertex(p->org);
2735 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2736 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2737 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2739 // for premultiplied alpha we have to apply the alpha to the color (after fog of course)
2740 VectorScale(c4f, alpha, c4f);
2743 // copy the color into the other three vertices
2744 Vector4Copy(c4f, c4f + 4);
2745 Vector4Copy(c4f, c4f + 8);
2746 Vector4Copy(c4f, c4f + 12);
2748 size = p->size * cl_particles_size.value;
2749 tex = &particletexture[p->texnum];
2750 switch(p->orientation)
2752 // case PARTICLE_INVALID:
2753 case PARTICLE_BILLBOARD:
2754 if (p->angle + p->spin)
2756 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2757 spinsin = sin(spinrad) * size;
2758 spincos = cos(spinrad) * size;
2759 spinm1 = -p->stretch * spincos;
2762 spinm4 = -p->stretch * spincos;
2763 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2764 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2768 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2769 VectorScale(r_refdef.view.up, size, up);
2772 v3f[ 0] = p->org[0] - right[0] - up[0];
2773 v3f[ 1] = p->org[1] - right[1] - up[1];
2774 v3f[ 2] = p->org[2] - right[2] - up[2];
2775 v3f[ 3] = p->org[0] - right[0] + up[0];
2776 v3f[ 4] = p->org[1] - right[1] + up[1];
2777 v3f[ 5] = p->org[2] - right[2] + up[2];
2778 v3f[ 6] = p->org[0] + right[0] + up[0];
2779 v3f[ 7] = p->org[1] + right[1] + up[1];
2780 v3f[ 8] = p->org[2] + right[2] + up[2];
2781 v3f[ 9] = p->org[0] + right[0] - up[0];
2782 v3f[10] = p->org[1] + right[1] - up[1];
2783 v3f[11] = p->org[2] + right[2] - up[2];
2784 t2f[0] = tex->s1;t2f[1] = tex->t2;
2785 t2f[2] = tex->s1;t2f[3] = tex->t1;
2786 t2f[4] = tex->s2;t2f[5] = tex->t1;
2787 t2f[6] = tex->s2;t2f[7] = tex->t2;
2789 case PARTICLE_ORIENTED_DOUBLESIDED:
2790 vecvel[0] = p->vel[0];
2791 vecvel[1] = p->vel[1];
2792 vecvel[2] = p->vel[2];
2793 VectorVectors(vecvel, baseright, baseup);
2794 if (p->angle + p->spin)
2796 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2797 spinsin = sin(spinrad) * size;
2798 spincos = cos(spinrad) * size;
2799 spinm1 = p->stretch * spincos;
2802 spinm4 = p->stretch * spincos;
2803 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2804 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2808 VectorScale(baseright, size * p->stretch, right);
2809 VectorScale(baseup, size, up);
2811 v3f[ 0] = p->org[0] - right[0] - up[0];
2812 v3f[ 1] = p->org[1] - right[1] - up[1];
2813 v3f[ 2] = p->org[2] - right[2] - up[2];
2814 v3f[ 3] = p->org[0] - right[0] + up[0];
2815 v3f[ 4] = p->org[1] - right[1] + up[1];
2816 v3f[ 5] = p->org[2] - right[2] + up[2];
2817 v3f[ 6] = p->org[0] + right[0] + up[0];
2818 v3f[ 7] = p->org[1] + right[1] + up[1];
2819 v3f[ 8] = p->org[2] + right[2] + up[2];
2820 v3f[ 9] = p->org[0] + right[0] - up[0];
2821 v3f[10] = p->org[1] + right[1] - up[1];
2822 v3f[11] = p->org[2] + right[2] - up[2];
2823 t2f[0] = tex->s1;t2f[1] = tex->t2;
2824 t2f[2] = tex->s1;t2f[3] = tex->t1;
2825 t2f[4] = tex->s2;t2f[5] = tex->t1;
2826 t2f[6] = tex->s2;t2f[7] = tex->t2;
2828 case PARTICLE_SPARK:
2829 len = VectorLength(p->vel);
2830 VectorNormalize2(p->vel, up);
2831 lenfactor = p->stretch * 0.04 * len;
2832 if(lenfactor < size * 0.5)
2833 lenfactor = size * 0.5;
2834 VectorMA(p->org, -lenfactor, up, v);
2835 VectorMA(p->org, lenfactor, up, up2);
2836 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2837 t2f[0] = tex->s1;t2f[1] = tex->t2;
2838 t2f[2] = tex->s1;t2f[3] = tex->t1;
2839 t2f[4] = tex->s2;t2f[5] = tex->t1;
2840 t2f[6] = tex->s2;t2f[7] = tex->t2;
2842 case PARTICLE_VBEAM:
2843 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2844 VectorSubtract(p->vel, p->org, up);
2845 VectorNormalize(up);
2846 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2847 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2848 t2f[0] = tex->s2;t2f[1] = v[0];
2849 t2f[2] = tex->s1;t2f[3] = v[0];
2850 t2f[4] = tex->s1;t2f[5] = v[1];
2851 t2f[6] = tex->s2;t2f[7] = v[1];
2853 case PARTICLE_HBEAM:
2854 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2855 VectorSubtract(p->vel, p->org, up);
2856 VectorNormalize(up);
2857 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2858 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2859 t2f[0] = v[0];t2f[1] = tex->t1;
2860 t2f[2] = v[0];t2f[3] = tex->t2;
2861 t2f[4] = v[1];t2f[5] = tex->t2;
2862 t2f[6] = v[1];t2f[7] = tex->t1;
2867 // now render batches of particles based on blendmode and texture
2868 blendmode = PBLEND_INVALID;
2872 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2873 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2875 p = cl.particles + surfacelist[surfacelistindex];
2877 if (texture != particletexture[p->texnum].texture)
2879 texture = particletexture[p->texnum].texture;
2880 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false);
2883 if (p->blendmode == PBLEND_INVMOD)
2885 // inverse modulate blend - group these
2886 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2887 // iterate until we find a change in settings
2888 batchstart = surfacelistindex++;
2889 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2891 p = cl.particles + surfacelist[surfacelistindex];
2892 if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2898 // additive or alpha blend - group these
2899 // (we can group these because we premultiplied the texture alpha)
2900 GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
2901 // iterate until we find a change in settings
2902 batchstart = surfacelistindex++;
2903 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2905 p = cl.particles + surfacelist[surfacelistindex];
2906 if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture)
2911 batchcount = surfacelistindex - batchstart;
2912 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2916 void R_DrawParticles (void)
2919 int drawparticles = r_drawparticles.integer;
2920 float minparticledist_start;
2922 float gravity, frametime, f, dist, oldorg[3], decaldir[3];
2928 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2929 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2931 // LordHavoc: early out conditions
2932 if (!cl.num_particles)
2935 minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value;
2936 gravity = frametime * cl.movevars_gravity;
2937 update = frametime > 0;
2938 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2939 drawdist2 = drawdist2*drawdist2;
2941 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2945 if (cl.free_particle > i)
2946 cl.free_particle = i;
2952 if (p->delayedspawn > cl.time)
2955 p->size += p->sizeincrease * frametime;
2956 p->alpha -= p->alphafade * frametime;
2958 if (p->alpha <= 0 || p->die <= cl.time)
2961 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2963 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2965 if (p->typeindex == pt_blood)
2966 p->size += frametime * 8;
2968 p->vel[2] -= p->gravity * gravity;
2969 f = 1.0f - min(p->liquidfriction * frametime, 1);
2970 VectorScale(p->vel, f, p->vel);
2974 p->vel[2] -= p->gravity * gravity;
2977 f = 1.0f - min(p->airfriction * frametime, 1);
2978 VectorScale(p->vel, f, p->vel);
2982 VectorCopy(p->org, oldorg);
2983 VectorMA(p->org, frametime, p->vel, p->org);
2984 // if (p->bounce && cl.time >= p->delayedcollisions)
2985 if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel))
2987 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), collision_extendmovelength.value, true, false, &hitent, false, false);
2988 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2989 // or if the trace hit something flagged as NOIMPACT
2990 // then remove the particle
2991 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2993 VectorCopy(trace.endpos, p->org);
2994 // react if the particle hit something
2995 if (trace.fraction < 1)
2997 VectorCopy(trace.endpos, p->org);
2999 if (p->staintexnum >= 0)
3001 // blood - splash on solid
3002 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
3005 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
3006 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
3007 if (cl_decals.integer)
3009 // create a decal for the blood splat
3010 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
3011 if (cl_decals_newsystem_bloodsmears.integer)
3013 VectorCopy(p->vel, decaldir);
3014 VectorNormalize(decaldir);
3017 VectorCopy(trace.plane.normal, decaldir);
3018 CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
3023 if (p->typeindex == pt_blood)
3025 // blood - splash on solid
3026 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
3028 if(p->staintexnum == -1) // staintex < -1 means no stains at all
3030 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)));
3031 if (cl_decals.integer)
3033 // create a decal for the blood splat
3034 if (cl_decals_newsystem_bloodsmears.integer)
3036 VectorCopy(p->vel, decaldir);
3037 VectorNormalize(decaldir);
3040 VectorCopy(trace.plane.normal, decaldir);
3041 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);
3046 else if (p->bounce < 0)
3048 // bounce -1 means remove on impact
3053 // anything else - bounce off solid
3054 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
3055 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
3060 if (VectorLength2(p->vel) < 0.03)
3062 if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off
3064 VectorClear(p->vel);
3068 if (p->typeindex != pt_static)
3070 switch (p->typeindex)
3072 case pt_entityparticle:
3073 // particle that removes itself after one rendered frame
3080 a = CL_PointSuperContents(p->org);
3081 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
3085 a = CL_PointSuperContents(p->org);
3086 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
3090 a = CL_PointSuperContents(p->org);
3091 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3095 if (cl.time > p->time2)
3098 p->time2 = cl.time + (rand() & 3) * 0.1;
3099 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3100 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
3102 a = CL_PointSuperContents(p->org);
3103 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
3111 else if (p->delayedspawn > cl.time)
3115 // don't render particles too close to the view (they chew fillrate)
3116 // also don't render particles behind the view (useless)
3117 // further checks to cull to the frustum would be too slow here
3118 switch(p->typeindex)
3121 // beams have no culling
3122 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3125 if(cl_particles_visculling.integer)
3126 if (!r_refdef.viewcache.world_novis)
3127 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
3129 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
3131 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
3134 // anything else just has to be in front of the viewer and visible at this distance
3135 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
3136 R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
3143 if (cl.free_particle > i)
3144 cl.free_particle = i;
3147 // reduce cl.num_particles if possible
3148 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
3151 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
3153 particle_t *oldparticles = cl.particles;
3154 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
3155 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
3156 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
3157 Mem_Free(oldparticles);