2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
121 particleeffectinfo_t;
123 #define MAX_PARTICLEEFFECTNAME 256
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 #define MAX_PARTICLEEFFECTINFO 4096
128 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
130 static int particlepalette[256];
132 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
133 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
134 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
135 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
136 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
137 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
138 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
139 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
140 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
141 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
142 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
143 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
144 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
145 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
146 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
147 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
148 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
149 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
150 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
151 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
152 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
153 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
154 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
155 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
156 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
157 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
158 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
159 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
160 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
161 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
162 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
163 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
166 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
167 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
168 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
170 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
172 #define MAX_PARTICLETEXTURES 1024
173 // particletexture_t is a rectangle in the particlefonttexture
174 typedef struct particletexture_s
177 float s1, t1, s2, t2;
181 static rtexturepool_t *particletexturepool;
182 static rtexture_t *particlefonttexture;
183 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
184 skinframe_t *decalskinframe;
186 // texture numbers in particle font
187 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
188 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
189 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
190 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
191 static const int tex_rainsplash = 32;
192 static const int tex_particle = 63;
193 static const int tex_bubble = 62;
194 static const int tex_raindrop = 61;
195 static const int tex_beam = 60;
197 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
198 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
199 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
200 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
201 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
202 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
203 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
204 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
205 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
206 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
207 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
208 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
209 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
210 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
211 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
212 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
213 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
214 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
215 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
216 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
217 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
218 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
219 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
220 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
221 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
222 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
225 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
231 particleeffectinfo_t *info = NULL;
232 const char *text = textstart;
234 effectinfoindex = -1;
235 for (linenumber = 1;;linenumber++)
238 for (arrayindex = 0;arrayindex < 16;arrayindex++)
239 argv[arrayindex][0] = 0;
242 if (!COM_ParseToken_Simple(&text, true, false))
244 if (!strcmp(com_token, "\n"))
248 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
254 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
255 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
256 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
257 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
258 #define readfloat(var) checkparms(2);var = atof(argv[1])
259 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
260 if (!strcmp(argv[0], "effect"))
265 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
267 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
270 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
272 if (particleeffectname[effectnameindex][0])
274 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
279 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
283 // if we run out of names, abort
284 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
286 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
289 info = particleeffectinfo + effectinfoindex;
290 info->effectnameindex = effectnameindex;
291 info->particletype = pt_alphastatic;
292 info->blendmode = particletype[info->particletype].blendmode;
293 info->orientation = particletype[info->particletype].orientation;
294 info->tex[0] = tex_particle;
295 info->tex[1] = tex_particle;
296 info->color[0] = 0xFFFFFF;
297 info->color[1] = 0xFFFFFF;
301 info->alpha[1] = 256;
302 info->alpha[2] = 256;
303 info->time[0] = 9999;
304 info->time[1] = 9999;
305 VectorSet(info->lightcolor, 1, 1, 1);
306 info->lightshadow = true;
307 info->lighttime = 9999;
308 info->stretchfactor = 1;
309 info->staincolor[0] = (unsigned int)-1;
310 info->staincolor[1] = (unsigned int)-1;
311 info->staintex[0] = -1;
312 info->staintex[1] = -1;
314 else if (info == NULL)
316 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
319 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
320 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
321 else if (!strcmp(argv[0], "type"))
324 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
325 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
326 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
327 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
328 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
329 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
330 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
331 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
332 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
333 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
334 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
335 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
336 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
337 info->blendmode = particletype[info->particletype].blendmode;
338 info->orientation = particletype[info->particletype].orientation;
340 else if (!strcmp(argv[0], "blend"))
343 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
344 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
345 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
346 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
348 else if (!strcmp(argv[0], "orientation"))
351 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
352 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
353 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
354 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
355 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
357 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
358 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
359 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
360 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
361 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
362 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
363 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
364 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
365 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
366 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
367 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
368 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
369 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
370 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
371 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
372 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
373 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
374 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
375 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
376 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
377 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
378 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
379 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
380 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
381 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
382 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
383 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
384 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
386 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
395 int CL_ParticleEffectIndexForName(const char *name)
398 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
399 if (!strcmp(particleeffectname[i], name))
404 const char *CL_ParticleEffectNameForIndex(int i)
406 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
408 return particleeffectname[i];
411 // MUST match effectnameindex_t in client.h
412 static const char *standardeffectnames[EFFECT_TOTAL] =
436 "TE_TEI_BIGEXPLOSION",
452 void CL_Particles_LoadEffectInfo(void)
455 unsigned char *filedata;
456 fs_offset_t filesize;
457 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
458 memset(particleeffectname, 0, sizeof(particleeffectname));
459 for (i = 0;i < EFFECT_TOTAL;i++)
460 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
461 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
464 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
474 void CL_ReadPointFile_f (void);
475 void CL_Particles_Init (void)
477 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)");
478 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
480 Cvar_RegisterVariable (&cl_particles);
481 Cvar_RegisterVariable (&cl_particles_quality);
482 Cvar_RegisterVariable (&cl_particles_alpha);
483 Cvar_RegisterVariable (&cl_particles_size);
484 Cvar_RegisterVariable (&cl_particles_quake);
485 Cvar_RegisterVariable (&cl_particles_blood);
486 Cvar_RegisterVariable (&cl_particles_blood_alpha);
487 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
488 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
489 Cvar_RegisterVariable (&cl_particles_explosions_shell);
490 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
491 Cvar_RegisterVariable (&cl_particles_rain);
492 Cvar_RegisterVariable (&cl_particles_snow);
493 Cvar_RegisterVariable (&cl_particles_smoke);
494 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
495 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
496 Cvar_RegisterVariable (&cl_particles_sparks);
497 Cvar_RegisterVariable (&cl_particles_bubbles);
498 Cvar_RegisterVariable (&cl_particles_visculling);
499 Cvar_RegisterVariable (&cl_decals);
500 Cvar_RegisterVariable (&cl_decals_visculling);
501 Cvar_RegisterVariable (&cl_decals_time);
502 Cvar_RegisterVariable (&cl_decals_fadetime);
503 Cvar_RegisterVariable (&cl_decals_newsystem);
504 Cvar_RegisterVariable (&cl_decals_models);
505 Cvar_RegisterVariable (&cl_decals_bias);
508 void CL_Particles_Shutdown (void)
512 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
513 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
515 // list of all 26 parameters:
516 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
517 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
518 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
519 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
520 // palpha - opacity of particle as 0-255 (can be more than 255)
521 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
522 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
523 // pgravity - how much effect gravity has on the particle (0-1)
524 // 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
525 // px,py,pz - starting origin of particle
526 // pvx,pvy,pvz - starting velocity of particle
527 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
528 // blendmode - one of the PBLEND_ values
529 // orientation - one of the PARTICLE_ values
530 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
531 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
532 particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex)
537 if (!cl_particles.integer)
539 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
540 if (cl.free_particle >= cl.max_particles)
543 lifetime = palpha / min(1, palphafade);
544 part = &cl.particles[cl.free_particle++];
545 if (cl.num_particles < cl.free_particle)
546 cl.num_particles = cl.free_particle;
547 memset(part, 0, sizeof(*part));
548 part->typeindex = ptypeindex;
549 part->blendmode = blendmode;
550 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
552 particletexture_t *tex = &particletexture[ptex];
553 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
554 part->orientation = PARTICLE_VBEAM;
556 part->orientation = PARTICLE_HBEAM;
559 part->orientation = orientation;
560 l2 = (int)lhrandom(0.5, 256.5);
562 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
563 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
564 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
565 part->staintexnum = staintex;
566 if(staincolor1 >= 0 && staincolor2 >= 0)
568 l2 = (int)lhrandom(0.5, 256.5);
570 if(blendmode == PBLEND_INVMOD)
572 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
573 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
574 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
578 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
579 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
580 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
582 if(r > 0xFF) r = 0xFF;
583 if(g > 0xFF) g = 0xFF;
584 if(b > 0xFF) b = 0xFF;
588 r = part->color[0]; // -1 is shorthand for stain = particle color
592 part->staincolor = r * 65536 + g * 256 + b;
595 part->sizeincrease = psizeincrease;
596 part->alpha = palpha;
597 part->alphafade = palphafade;
598 part->gravity = pgravity;
599 part->bounce = pbounce;
600 part->stretch = stretch;
602 part->org[0] = px + originjitter * v[0];
603 part->org[1] = py + originjitter * v[1];
604 part->org[2] = pz + originjitter * v[2];
605 part->vel[0] = pvx + velocityjitter * v[0];
606 part->vel[1] = pvy + velocityjitter * v[1];
607 part->vel[2] = pvz + velocityjitter * v[2];
609 part->airfriction = pairfriction;
610 part->liquidfriction = pliquidfriction;
611 part->die = cl.time + lifetime;
612 part->delayedcollisions = 0;
613 part->qualityreduction = pqualityreduction;
614 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
615 if (part->typeindex == pt_rain)
619 float lifetime = part->die - cl.time;
622 // turn raindrop into simple spark and create delayedspawn splash effect
623 part->typeindex = pt_spark;
625 VectorMA(part->org, lifetime, part->vel, endvec);
626 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
627 part->die = cl.time + lifetime * trace.fraction;
628 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1);
631 part2->delayedspawn = part->die;
632 part2->die += part->die - cl.time;
633 for (i = rand() & 7;i < 10;i++)
635 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
638 part2->delayedspawn = part->die;
639 part2->die += part->die - cl.time;
644 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
646 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
649 VectorMA(part->org, lifetime, part->vel, endvec);
650 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
651 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
657 static void CL_ImmediateBloodStain(particle_t *part)
662 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
663 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
665 VectorCopy(part->vel, v);
667 staintex = part->staintexnum;
668 R_DecalSystem_SplatEntities(part->org, v, 1-((part->staincolor>>16)&255)*(1.0f/255.0f), 1-((part->staincolor>>8)&255)*(1.0f/255.0f), 1-((part->staincolor)&255)*(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);
671 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
672 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
674 VectorCopy(part->vel, v);
676 staintex = tex_blooddecal[rand()&7];
677 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);
681 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
685 entity_render_t *ent = &cl.entities[hitent].render;
686 unsigned char color[3];
687 if (!cl_decals.integer)
689 if (!ent->allowdecals)
692 l2 = (int)lhrandom(0.5, 256.5);
694 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
695 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
696 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
698 if (cl_decals_newsystem.integer)
700 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);
704 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
705 if (cl.free_decal >= cl.max_decals)
707 decal = &cl.decals[cl.free_decal++];
708 if (cl.num_decals < cl.free_decal)
709 cl.num_decals = cl.free_decal;
710 memset(decal, 0, sizeof(*decal));
711 decal->typeindex = pt_decal;
712 decal->texnum = texnum;
713 VectorMA(org, cl_decals_bias.value, normal, decal->org);
714 VectorCopy(normal, decal->normal);
716 decal->alpha = alpha;
717 decal->time2 = cl.time;
718 decal->color[0] = color[0];
719 decal->color[1] = color[1];
720 decal->color[2] = color[2];
721 decal->owner = hitent;
722 decal->clusterindex = -1000; // no vis culling unless we're sure
725 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
726 decal->ownermodel = cl.entities[decal->owner].render.model;
727 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
728 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
732 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
734 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
736 decal->clusterindex = leaf->clusterindex;
741 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
744 float bestfrac, bestorg[3], bestnormal[3];
746 int besthitent = 0, hitent;
749 for (i = 0;i < 32;i++)
752 VectorMA(org, maxdist, org2, org2);
753 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
754 // take the closest trace result that doesn't end up hitting a NOMARKS
755 // surface (sky for example)
756 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
758 bestfrac = trace.fraction;
760 VectorCopy(trace.endpos, bestorg);
761 VectorCopy(trace.plane.normal, bestnormal);
765 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
768 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
769 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
770 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)
773 matrix4x4_t tempmatrix;
775 VectorLerp(originmins, 0.5, originmaxs, center);
776 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
777 if (effectnameindex == EFFECT_SVC_PARTICLE)
779 if (cl_particles.integer)
781 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
783 CL_ParticleExplosion(center);
784 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
785 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
788 count *= cl_particles_quality.value;
789 for (;count > 0;count--)
791 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
792 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
797 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
798 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
799 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
800 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
801 else if (effectnameindex == EFFECT_TE_SPIKE)
803 if (cl_particles_bulletimpacts.integer)
805 if (cl_particles_quake.integer)
807 if (cl_particles_smoke.integer)
808 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
812 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
813 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
814 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
818 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
819 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
821 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
823 if (cl_particles_bulletimpacts.integer)
825 if (cl_particles_quake.integer)
827 if (cl_particles_smoke.integer)
828 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
832 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
833 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
834 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
838 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
839 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
840 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);
842 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
844 if (cl_particles_bulletimpacts.integer)
846 if (cl_particles_quake.integer)
848 if (cl_particles_smoke.integer)
849 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
853 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
854 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
855 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
859 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
860 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
862 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
864 if (cl_particles_bulletimpacts.integer)
866 if (cl_particles_quake.integer)
868 if (cl_particles_smoke.integer)
869 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
873 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
874 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
875 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
879 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
880 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
881 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);
883 else if (effectnameindex == EFFECT_TE_BLOOD)
885 if (!cl_particles_blood.integer)
887 if (cl_particles_quake.integer)
888 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
891 static double bloodaccumulator = 0;
892 qboolean immediatebloodstain = true;
893 //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
894 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
895 for (;bloodaccumulator > 0;bloodaccumulator--)
897 part = CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
898 if (immediatebloodstain && part)
900 immediatebloodstain = false;
901 CL_ImmediateBloodStain(part);
906 else if (effectnameindex == EFFECT_TE_SPARK)
907 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
908 else if (effectnameindex == EFFECT_TE_PLASMABURN)
910 // plasma scorch mark
911 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
912 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
913 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
915 else if (effectnameindex == EFFECT_TE_GUNSHOT)
917 if (cl_particles_bulletimpacts.integer)
919 if (cl_particles_quake.integer)
920 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
923 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
924 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
925 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
929 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
930 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
932 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
934 if (cl_particles_bulletimpacts.integer)
936 if (cl_particles_quake.integer)
937 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
940 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
941 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
942 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
946 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
947 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
948 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);
950 else if (effectnameindex == EFFECT_TE_EXPLOSION)
952 CL_ParticleExplosion(center);
953 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);
955 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
957 CL_ParticleExplosion(center);
958 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);
960 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
962 if (cl_particles_quake.integer)
965 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
968 CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
970 CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
974 CL_ParticleExplosion(center);
975 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);
977 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
978 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);
979 else if (effectnameindex == EFFECT_TE_FLAMEJET)
981 count *= cl_particles_quality.value;
983 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
985 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
987 float i, j, inc, vel;
990 inc = 8 / cl_particles_quality.value;
991 for (i = -128;i < 128;i += inc)
993 for (j = -128;j < 128;j += inc)
995 dir[0] = j + lhrandom(0, inc);
996 dir[1] = i + lhrandom(0, inc);
998 org[0] = center[0] + dir[0];
999 org[1] = center[1] + dir[1];
1000 org[2] = center[2] + lhrandom(0, 64);
1001 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1002 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1006 else if (effectnameindex == EFFECT_TE_TELEPORT)
1008 float i, j, k, inc, vel;
1011 if (cl_particles_quake.integer)
1012 inc = 4 / cl_particles_quality.value;
1014 inc = 8 / cl_particles_quality.value;
1015 for (i = -16;i < 16;i += inc)
1017 for (j = -16;j < 16;j += inc)
1019 for (k = -24;k < 32;k += inc)
1021 VectorSet(dir, i*8, j*8, k*8);
1022 VectorNormalize(dir);
1023 vel = lhrandom(50, 113);
1024 if (cl_particles_quake.integer)
1025 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1027 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1031 if (!cl_particles_quake.integer)
1032 CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1033 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);
1035 else if (effectnameindex == EFFECT_TE_TEI_G3)
1036 CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1037 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1039 if (cl_particles_smoke.integer)
1041 count *= 0.25f * cl_particles_quality.value;
1043 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1046 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1048 CL_ParticleExplosion(center);
1049 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);
1051 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1054 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1055 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1056 if (cl_particles_smoke.integer)
1057 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1058 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1059 if (cl_particles_sparks.integer)
1060 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1061 CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1062 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);
1064 else if (effectnameindex == EFFECT_EF_FLAME)
1066 count *= 300 * cl_particles_quality.value;
1068 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1069 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);
1071 else if (effectnameindex == EFFECT_EF_STARDUST)
1073 count *= 200 * cl_particles_quality.value;
1075 CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1076 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);
1078 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1082 int smoke, blood, bubbles, r, color;
1084 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1087 Vector4Set(light, 0, 0, 0, 0);
1089 if (effectnameindex == EFFECT_TR_ROCKET)
1090 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1091 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1093 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1094 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1096 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1098 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1099 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1103 matrix4x4_t tempmatrix;
1104 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1105 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);
1106 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1110 if (!spawnparticles)
1113 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1116 VectorSubtract(originmaxs, originmins, dir);
1117 len = VectorNormalizeLength(dir);
1120 dec = -ent->persistent.trail_time;
1121 ent->persistent.trail_time += len;
1122 if (ent->persistent.trail_time < 0.01f)
1125 // if we skip out, leave it reset
1126 ent->persistent.trail_time = 0.0f;
1131 // advance into this frame to reach the first puff location
1132 VectorMA(originmins, dec, dir, pos);
1135 smoke = cl_particles.integer && cl_particles_smoke.integer;
1136 blood = cl_particles.integer && cl_particles_blood.integer;
1137 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1138 qd = 1.0f / cl_particles_quality.value;
1145 if (effectnameindex == EFFECT_TR_BLOOD)
1147 if (cl_particles_quake.integer)
1149 color = particlepalette[67 + (rand()&3)];
1150 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1155 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1158 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1160 if (cl_particles_quake.integer)
1163 color = particlepalette[67 + (rand()&3)];
1164 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1169 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1175 if (effectnameindex == EFFECT_TR_ROCKET)
1177 if (cl_particles_quake.integer)
1180 color = particlepalette[ramp3[r]];
1181 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1185 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1186 CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1189 else if (effectnameindex == EFFECT_TR_GRENADE)
1191 if (cl_particles_quake.integer)
1194 color = particlepalette[ramp3[r]];
1195 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1199 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1202 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1204 if (cl_particles_quake.integer)
1207 color = particlepalette[52 + (rand()&7)];
1208 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1209 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1211 else if (gamemode == GAME_GOODVSBAD2)
1214 CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1218 color = particlepalette[20 + (rand()&7)];
1219 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1222 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1224 if (cl_particles_quake.integer)
1227 color = particlepalette[230 + (rand()&7)];
1228 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1229 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1233 color = particlepalette[226 + (rand()&7)];
1234 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1237 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1239 if (cl_particles_quake.integer)
1241 color = particlepalette[152 + (rand()&3)];
1242 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1244 else if (gamemode == GAME_GOODVSBAD2)
1247 CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1249 else if (gamemode == GAME_PRYDON)
1252 CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1255 CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1257 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1260 CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1262 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1265 CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1267 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1268 CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1272 if (effectnameindex == EFFECT_TR_ROCKET)
1273 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1274 else if (effectnameindex == EFFECT_TR_GRENADE)
1275 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1277 // advance to next time and position
1280 VectorMA (pos, dec, dir, pos);
1283 ent->persistent.trail_time = len;
1285 else if (developer.integer >= 1)
1286 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1289 // this is also called on point effects with spawndlight = true and
1290 // spawnparticles = true
1291 // it is called CL_ParticleTrail because most code does not want to supply
1292 // these parameters, only trail handling does
1293 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)
1296 qboolean found = false;
1297 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1299 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1300 return; // no such effect
1302 VectorLerp(originmins, 0.5, originmaxs, center);
1303 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1305 int effectinfoindex;
1308 particleeffectinfo_t *info;
1310 vec3_t centervelocity;
1316 qboolean underwater;
1317 qboolean immediatebloodstain;
1319 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1320 VectorLerp(originmins, 0.5, originmaxs, center);
1321 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1322 supercontents = CL_PointSuperContents(center);
1323 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1324 VectorSubtract(originmaxs, originmins, traildir);
1325 traillen = VectorLength(traildir);
1326 VectorNormalize(traildir);
1327 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1329 if (info->effectnameindex == effectnameindex)
1332 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1334 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1337 // spawn a dlight if requested
1338 if (info->lightradiusstart > 0 && spawndlight)
1340 matrix4x4_t tempmatrix;
1341 if (info->trailspacing > 0)
1342 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1344 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1345 if (info->lighttime > 0 && info->lightradiusfade > 0)
1347 // light flash (explosion, etc)
1348 // called when effect starts
1349 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1354 // called by CL_LinkNetworkEntity
1355 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1356 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1357 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1361 if (!spawnparticles)
1366 if (info->tex[1] > info->tex[0])
1368 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1369 tex = min(tex, info->tex[1] - 1);
1371 if(info->staintex[0] < 0)
1372 staintex = info->staintex[0];
1375 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1376 staintex = min(staintex, info->staintex[1] - 1);
1378 if (info->particletype == pt_decal)
1379 CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
1380 else if (info->orientation == PARTICLE_HBEAM)
1381 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1384 if (!cl_particles.integer)
1386 switch (info->particletype)
1388 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1389 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1390 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1391 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1392 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1393 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1396 VectorCopy(originmins, trailpos);
1397 if (info->trailspacing > 0)
1399 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1400 trailstep = info->trailspacing / cl_particles_quality.value;
1401 immediatebloodstain = false;
1405 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1407 immediatebloodstain = info->particletype == pt_blood || staintex;
1409 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1410 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1412 if (info->tex[1] > info->tex[0])
1414 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1415 tex = min(tex, info->tex[1] - 1);
1419 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1420 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1421 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1424 part = CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1425 if (immediatebloodstain && part)
1427 immediatebloodstain = false;
1428 CL_ImmediateBloodStain(part);
1431 VectorMA(trailpos, trailstep, traildir, trailpos);
1438 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1441 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)
1443 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1451 void CL_EntityParticles (const entity_t *ent)
1454 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1455 static vec3_t avelocities[NUMVERTEXNORMALS];
1456 if (!cl_particles.integer) return;
1457 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1459 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1461 if (!avelocities[0][0])
1462 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1463 avelocities[0][i] = lhrandom(0, 2.55);
1465 for (i = 0;i < NUMVERTEXNORMALS;i++)
1467 yaw = cl.time * avelocities[i][0];
1468 pitch = cl.time * avelocities[i][1];
1469 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1470 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1471 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1472 CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1477 void CL_ReadPointFile_f (void)
1479 vec3_t org, leakorg;
1481 char *pointfile = NULL, *pointfilepos, *t, tchar;
1482 char name[MAX_OSPATH];
1487 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1488 strlcat (name, ".pts", sizeof (name));
1489 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1492 Con_Printf("Could not open %s\n", name);
1496 Con_Printf("Reading %s...\n", name);
1497 VectorClear(leakorg);
1500 pointfilepos = pointfile;
1501 while (*pointfilepos)
1503 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1508 while (*t && *t != '\n' && *t != '\r')
1512 #if _MSC_VER >= 1400
1513 #define sscanf sscanf_s
1515 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1521 VectorCopy(org, leakorg);
1524 if (cl.num_particles < cl.max_particles - 3)
1527 CL_NewParticle(pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1530 Mem_Free(pointfile);
1531 VectorCopy(leakorg, org);
1532 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1534 CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1535 CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1536 CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1541 CL_ParseParticleEffect
1543 Parse an effect out of the server message
1546 void CL_ParseParticleEffect (void)
1549 int i, count, msgcount, color;
1551 MSG_ReadVector(org, cls.protocol);
1552 for (i=0 ; i<3 ; i++)
1553 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1554 msgcount = MSG_ReadByte ();
1555 color = MSG_ReadByte ();
1557 if (msgcount == 255)
1562 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1567 CL_ParticleExplosion
1571 void CL_ParticleExplosion (const vec3_t org)
1577 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1578 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1580 if (cl_particles_quake.integer)
1582 for (i = 0;i < 1024;i++)
1588 color = particlepalette[ramp1[r]];
1589 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1593 color = particlepalette[ramp2[r]];
1594 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1600 i = CL_PointSuperContents(org);
1601 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1603 if (cl_particles.integer && cl_particles_bubbles.integer)
1604 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1605 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1609 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1611 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1615 for (k = 0;k < 16;k++)
1618 VectorMA(org, 128, v2, v);
1619 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1620 if (trace.fraction >= 0.1)
1623 VectorSubtract(trace.endpos, org, v2);
1624 VectorScale(v2, 2.0f, v2);
1625 CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1631 if (cl_particles_explosions_shell.integer)
1632 R_NewExplosion(org);
1637 CL_ParticleExplosion2
1641 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1644 if (!cl_particles.integer) return;
1646 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1648 k = particlepalette[colorStart + (i % colorLength)];
1649 if (cl_particles_quake.integer)
1650 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1652 CL_NewParticle(pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1656 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1658 if (cl_particles_sparks.integer)
1660 sparkcount *= cl_particles_quality.value;
1661 while(sparkcount-- > 0)
1662 CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1666 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1668 if (cl_particles_smoke.integer)
1670 smokecount *= cl_particles_quality.value;
1671 while(smokecount-- > 0)
1672 CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1676 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)
1679 if (!cl_particles.integer) return;
1681 count = (int)(count * cl_particles_quality.value);
1684 k = particlepalette[colorbase + (rand()&3)];
1685 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1689 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1692 float minz, maxz, lifetime = 30;
1693 if (!cl_particles.integer) return;
1694 if (dir[2] < 0) // falling
1696 minz = maxs[2] + dir[2] * 0.1;
1699 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1704 maxz = maxs[2] + dir[2] * 0.1;
1706 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1709 count = (int)(count * cl_particles_quality.value);
1714 if (!cl_particles_rain.integer) break;
1715 count *= 4; // ick, this should be in the mod or maps?
1719 k = particlepalette[colorbase + (rand()&3)];
1720 if (gamemode == GAME_GOODVSBAD2)
1721 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1723 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1727 if (!cl_particles_snow.integer) break;
1730 k = particlepalette[colorbase + (rand()&3)];
1731 if (gamemode == GAME_GOODVSBAD2)
1732 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1734 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1738 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1742 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1743 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1744 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1745 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1747 #define PARTICLETEXTURESIZE 64
1748 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1750 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1754 dz = 1 - (dx*dx+dy*dy);
1755 if (dz > 0) // it does hit the sphere
1759 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1760 VectorNormalize(normal);
1761 dot = DotProduct(normal, light);
1762 if (dot > 0.5) // interior reflection
1763 f += ((dot * 2) - 1);
1764 else if (dot < -0.5) // exterior reflection
1765 f += ((dot * -2) - 1);
1767 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1768 VectorNormalize(normal);
1769 dot = DotProduct(normal, light);
1770 if (dot > 0.5) // interior reflection
1771 f += ((dot * 2) - 1);
1772 else if (dot < -0.5) // exterior reflection
1773 f += ((dot * -2) - 1);
1775 f += 16; // just to give it a haze so you can see the outline
1776 f = bound(0, f, 255);
1777 return (unsigned char) f;
1783 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1784 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1786 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1787 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1788 *width = particlefontcellwidth;
1789 *height = particlefontcellheight;
1792 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1794 int basex, basey, w, h, y;
1795 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1796 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1797 Sys_Error("invalid particle texture size for autogenerating");
1798 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1799 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1802 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1805 float cx, cy, dx, dy, f, iradius;
1807 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1808 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1809 iradius = 1.0f / radius;
1810 alpha *= (1.0f / 255.0f);
1811 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1813 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1817 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1822 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1823 d[0] += (int)(f * (blue - d[0]));
1824 d[1] += (int)(f * (green - d[1]));
1825 d[2] += (int)(f * (red - d[2]));
1831 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1834 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1836 data[0] = bound(minb, data[0], maxb);
1837 data[1] = bound(ming, data[1], maxg);
1838 data[2] = bound(minr, data[2], maxr);
1842 void particletextureinvert(unsigned char *data)
1845 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1847 data[0] = 255 - data[0];
1848 data[1] = 255 - data[1];
1849 data[2] = 255 - data[2];
1853 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1854 static void R_InitBloodTextures (unsigned char *particletexturedata)
1857 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1860 for (i = 0;i < 8;i++)
1862 memset(&data[0][0][0], 255, sizeof(data));
1863 for (k = 0;k < 24;k++)
1864 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1865 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1866 particletextureinvert(&data[0][0][0]);
1867 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1871 for (i = 0;i < 8;i++)
1873 memset(&data[0][0][0], 255, sizeof(data));
1875 for (j = 1;j < 10;j++)
1876 for (k = min(j, m - 1);k < m;k++)
1877 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1878 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1879 particletextureinvert(&data[0][0][0]);
1880 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1885 //uncomment this to make engine save out particle font to a tga file when run
1886 //#define DUMPPARTICLEFONT
1888 static void R_InitParticleTexture (void)
1890 int x, y, d, i, k, m;
1891 int basex, basey, w, h;
1895 fs_offset_t filesize;
1897 // a note: decals need to modulate (multiply) the background color to
1898 // properly darken it (stain), and they need to be able to alpha fade,
1899 // this is a very difficult challenge because it means fading to white
1900 // (no change to background) rather than black (darkening everything
1901 // behind the whole decal polygon), and to accomplish this the texture is
1902 // inverted (dark red blood on white background becomes brilliant cyan
1903 // and white on black background) so we can alpha fade it to black, then
1904 // we invert it again during the blendfunc to make it work...
1906 #ifndef DUMPPARTICLEFONT
1907 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
1910 particlefonttexture = decalskinframe->base;
1911 // TODO maybe allow custom grid size?
1912 particlefontwidth = image_width;
1913 particlefontheight = image_height;
1914 particlefontcellwidth = image_width / 8;
1915 particlefontcellheight = image_height / 8;
1916 particlefontcols = 8;
1917 particlefontrows = 8;
1922 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1923 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1925 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1926 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1927 particlefontcols = 8;
1928 particlefontrows = 8;
1930 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1933 for (i = 0;i < 8;i++)
1935 memset(&data[0][0][0], 255, sizeof(data));
1938 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1940 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1941 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1943 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1945 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1946 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1948 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1949 d = (noise2[y][x] - 128) * 3 + 192;
1951 d = (int)(d * (1-(dx*dx+dy*dy)));
1952 d = (d * noise1[y][x]) >> 7;
1953 d = bound(0, d, 255);
1954 data[y][x][3] = (unsigned char) d;
1961 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1965 memset(&data[0][0][0], 255, sizeof(data));
1966 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1968 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1969 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1971 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1972 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1973 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1976 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1979 memset(&data[0][0][0], 255, sizeof(data));
1980 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1982 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1983 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1985 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1986 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1987 d = bound(0, d, 255);
1988 data[y][x][3] = (unsigned char) d;
1991 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1994 memset(&data[0][0][0], 255, sizeof(data));
1995 light[0] = 1;light[1] = 1;light[2] = 1;
1996 VectorNormalize(light);
1997 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1999 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2000 // stretch upper half of bubble by +50% and shrink lower half by -50%
2001 // (this gives an elongated teardrop shape)
2003 dy = (dy - 0.5f) * 2.0f;
2005 dy = (dy - 0.5f) / 1.5f;
2006 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2008 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2009 // shrink bubble width to half
2011 data[y][x][3] = shadebubble(dx, dy, light);
2014 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2017 memset(&data[0][0][0], 255, sizeof(data));
2018 light[0] = 1;light[1] = 1;light[2] = 1;
2019 VectorNormalize(light);
2020 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2022 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2023 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2025 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2026 data[y][x][3] = shadebubble(dx, dy, light);
2029 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2031 // Blood particles and blood decals
2032 R_InitBloodTextures (particletexturedata);
2035 for (i = 0;i < 8;i++)
2037 memset(&data[0][0][0], 255, sizeof(data));
2038 for (k = 0;k < 12;k++)
2039 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2040 for (k = 0;k < 3;k++)
2041 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2042 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2043 particletextureinvert(&data[0][0][0]);
2044 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2047 #ifdef DUMPPARTICLEFONT
2048 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2051 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2052 particlefonttexture = decalskinframe->base;
2054 Mem_Free(particletexturedata);
2056 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2058 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2059 particletexture[i].texture = particlefonttexture;
2060 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2061 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2062 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2063 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2066 #ifndef DUMPPARTICLEFONT
2067 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
2068 if (!particletexture[tex_beam].texture)
2071 unsigned char noise3[64][64], data2[64][16][4];
2073 fractalnoise(&noise3[0][0], 64, 4);
2075 for (y = 0;y < 64;y++)
2077 dy = (y - 0.5f*64) / (64*0.5f-1);
2078 for (x = 0;x < 16;x++)
2080 dx = (x - 0.5f*16) / (16*0.5f-2);
2081 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2082 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2083 data2[y][x][3] = 255;
2087 #ifdef DUMPPARTICLEFONT
2088 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2090 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2092 particletexture[tex_beam].s1 = 0;
2093 particletexture[tex_beam].t1 = 0;
2094 particletexture[tex_beam].s2 = 1;
2095 particletexture[tex_beam].t2 = 1;
2097 // now load an texcoord/texture override file
2098 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2105 if(!COM_ParseToken_Simple(&bufptr, true, false))
2107 if(!strcmp(com_token, "\n"))
2108 continue; // empty line
2109 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2110 particletexture[i].texture = particlefonttexture;
2112 if (!COM_ParseToken_Simple(&bufptr, true, false))
2114 if (!strcmp(com_token, "\n"))
2116 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2119 particletexture[i].s1 = atof(com_token);
2121 if (!COM_ParseToken_Simple(&bufptr, true, false))
2123 if (!strcmp(com_token, "\n"))
2125 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2128 particletexture[i].t1 = atof(com_token);
2130 if (!COM_ParseToken_Simple(&bufptr, true, false))
2132 if (!strcmp(com_token, "\n"))
2134 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2137 particletexture[i].s2 = atof(com_token);
2139 if (!COM_ParseToken_Simple(&bufptr, true, false))
2141 if (!strcmp(com_token, "\n"))
2143 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2146 particletexture[i].t2 = atof(com_token);
2152 static void r_part_start(void)
2155 // generate particlepalette for convenience from the main one
2156 for (i = 0;i < 256;i++)
2157 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2158 particletexturepool = R_AllocTexturePool();
2159 R_InitParticleTexture ();
2160 CL_Particles_LoadEffectInfo();
2163 static void r_part_shutdown(void)
2165 R_FreeTexturePool(&particletexturepool);
2168 static void r_part_newmap(void)
2171 R_SkinFrame_MarkUsed(decalskinframe);
2172 CL_Particles_LoadEffectInfo();
2175 #define BATCHSIZE 256
2176 unsigned short particle_elements[BATCHSIZE*6];
2178 void R_Particles_Init (void)
2181 for (i = 0;i < BATCHSIZE;i++)
2183 particle_elements[i*6+0] = i*4+0;
2184 particle_elements[i*6+1] = i*4+1;
2185 particle_elements[i*6+2] = i*4+2;
2186 particle_elements[i*6+3] = i*4+0;
2187 particle_elements[i*6+4] = i*4+2;
2188 particle_elements[i*6+5] = i*4+3;
2191 Cvar_RegisterVariable(&r_drawparticles);
2192 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2193 Cvar_RegisterVariable(&r_drawdecals);
2194 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2195 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2198 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2200 int surfacelistindex;
2202 float *v3f, *t2f, *c4f;
2203 particletexture_t *tex;
2204 float right[3], up[3], size, ca;
2205 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2206 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2208 RSurf_ActiveWorldEntity();
2210 r_refdef.stats.decals += numsurfaces;
2211 R_Mesh_ResetTextureState();
2212 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2213 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2214 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2215 R_SetupGenericShader(true);
2216 GL_DepthMask(false);
2217 GL_DepthRange(0, 1);
2218 GL_PolygonOffset(0, 0);
2220 GL_CullFace(GL_NONE);
2222 // generate all the vertices at once
2223 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2225 d = cl.decals + surfacelist[surfacelistindex];
2228 c4f = particle_color4f + 16*surfacelistindex;
2229 ca = d->alpha * alphascale;
2230 if (r_refdef.fogenabled)
2231 ca *= RSurf_FogVertex(d->org);
2232 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2233 Vector4Copy(c4f, c4f + 4);
2234 Vector4Copy(c4f, c4f + 8);
2235 Vector4Copy(c4f, c4f + 12);
2237 // calculate vertex positions
2238 size = d->size * cl_particles_size.value;
2239 VectorVectors(d->normal, right, up);
2240 VectorScale(right, size, right);
2241 VectorScale(up, size, up);
2242 v3f = particle_vertex3f + 12*surfacelistindex;
2243 v3f[ 0] = d->org[0] - right[0] - up[0];
2244 v3f[ 1] = d->org[1] - right[1] - up[1];
2245 v3f[ 2] = d->org[2] - right[2] - up[2];
2246 v3f[ 3] = d->org[0] - right[0] + up[0];
2247 v3f[ 4] = d->org[1] - right[1] + up[1];
2248 v3f[ 5] = d->org[2] - right[2] + up[2];
2249 v3f[ 6] = d->org[0] + right[0] + up[0];
2250 v3f[ 7] = d->org[1] + right[1] + up[1];
2251 v3f[ 8] = d->org[2] + right[2] + up[2];
2252 v3f[ 9] = d->org[0] + right[0] - up[0];
2253 v3f[10] = d->org[1] + right[1] - up[1];
2254 v3f[11] = d->org[2] + right[2] - up[2];
2256 // calculate texcoords
2257 tex = &particletexture[d->texnum];
2258 t2f = particle_texcoord2f + 8*surfacelistindex;
2259 t2f[0] = tex->s1;t2f[1] = tex->t2;
2260 t2f[2] = tex->s1;t2f[3] = tex->t1;
2261 t2f[4] = tex->s2;t2f[5] = tex->t1;
2262 t2f[6] = tex->s2;t2f[7] = tex->t2;
2265 // now render the decals all at once
2266 // (this assumes they all use one particle font texture!)
2267 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2268 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2269 GL_LockArrays(0, numsurfaces*4);
2270 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2271 GL_LockArrays(0, 0);
2274 void R_DrawDecals (void)
2277 int drawdecals = r_drawdecals.integer;
2283 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2284 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2286 // LordHavoc: early out conditions
2290 decalfade = frametime * 256 / cl_decals_fadetime.value;
2291 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2292 drawdist2 = drawdist2*drawdist2;
2294 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2296 if (!decal->typeindex)
2299 if (cl.time > decal->time2 + cl_decals_time.value)
2301 decal->alpha -= decalfade;
2302 if (decal->alpha <= 0)
2308 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2310 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2311 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2317 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2323 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))
2324 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2327 decal->typeindex = 0;
2328 if (cl.free_decal > i)
2332 // reduce cl.num_decals if possible
2333 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2336 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2338 decal_t *olddecals = cl.decals;
2339 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2340 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2341 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2342 Mem_Free(olddecals);
2346 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2348 int surfacelistindex;
2349 int batchstart, batchcount;
2350 const particle_t *p;
2352 rtexture_t *texture;
2353 float *v3f, *t2f, *c4f;
2354 particletexture_t *tex;
2355 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2356 float ambient[3], diffuse[3], diffusenormal[3];
2357 vec4_t colormultiplier;
2358 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2360 RSurf_ActiveWorldEntity();
2362 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));
2364 r_refdef.stats.particles += numsurfaces;
2365 R_Mesh_ResetTextureState();
2366 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2367 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2368 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2369 R_SetupGenericShader(true);
2370 GL_DepthMask(false);
2371 GL_DepthRange(0, 1);
2372 GL_PolygonOffset(0, 0);
2374 GL_CullFace(GL_NONE);
2376 // first generate all the vertices at once
2377 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2379 p = cl.particles + surfacelist[surfacelistindex];
2381 blendmode = p->blendmode;
2383 c4f[0] = p->color[0] * colormultiplier[0];
2384 c4f[1] = p->color[1] * colormultiplier[1];
2385 c4f[2] = p->color[2] * colormultiplier[2];
2386 c4f[3] = p->alpha * colormultiplier[3];
2389 case PBLEND_INVALID:
2392 // additive and modulate can just fade out in fog (this is correct)
2393 if (r_refdef.fogenabled)
2394 c4f[3] *= RSurf_FogVertex(p->org);
2395 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2402 // note: lighting is not cheap!
2403 if (particletype[p->typeindex].lighting)
2405 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2406 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2407 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2408 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2410 // mix in the fog color
2411 if (r_refdef.fogenabled)
2413 fog = RSurf_FogVertex(p->org);
2415 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2416 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2417 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2421 // copy the color into the other three vertices
2422 Vector4Copy(c4f, c4f + 4);
2423 Vector4Copy(c4f, c4f + 8);
2424 Vector4Copy(c4f, c4f + 12);
2426 size = p->size * cl_particles_size.value;
2427 tex = &particletexture[p->texnum];
2428 switch(p->orientation)
2430 case PARTICLE_INVALID:
2431 case PARTICLE_BILLBOARD:
2432 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2433 VectorScale(r_refdef.view.up, size, up);
2434 v3f[ 0] = p->org[0] - right[0] - up[0];
2435 v3f[ 1] = p->org[1] - right[1] - up[1];
2436 v3f[ 2] = p->org[2] - right[2] - up[2];
2437 v3f[ 3] = p->org[0] - right[0] + up[0];
2438 v3f[ 4] = p->org[1] - right[1] + up[1];
2439 v3f[ 5] = p->org[2] - right[2] + up[2];
2440 v3f[ 6] = p->org[0] + right[0] + up[0];
2441 v3f[ 7] = p->org[1] + right[1] + up[1];
2442 v3f[ 8] = p->org[2] + right[2] + up[2];
2443 v3f[ 9] = p->org[0] + right[0] - up[0];
2444 v3f[10] = p->org[1] + right[1] - up[1];
2445 v3f[11] = p->org[2] + right[2] - up[2];
2446 t2f[0] = tex->s1;t2f[1] = tex->t2;
2447 t2f[2] = tex->s1;t2f[3] = tex->t1;
2448 t2f[4] = tex->s2;t2f[5] = tex->t1;
2449 t2f[6] = tex->s2;t2f[7] = tex->t2;
2451 case PARTICLE_ORIENTED_DOUBLESIDED:
2452 VectorVectors(p->vel, right, up);
2453 VectorScale(right, size * p->stretch, right);
2454 VectorScale(up, size, up);
2455 v3f[ 0] = p->org[0] - right[0] - up[0];
2456 v3f[ 1] = p->org[1] - right[1] - up[1];
2457 v3f[ 2] = p->org[2] - right[2] - up[2];
2458 v3f[ 3] = p->org[0] - right[0] + up[0];
2459 v3f[ 4] = p->org[1] - right[1] + up[1];
2460 v3f[ 5] = p->org[2] - right[2] + up[2];
2461 v3f[ 6] = p->org[0] + right[0] + up[0];
2462 v3f[ 7] = p->org[1] + right[1] + up[1];
2463 v3f[ 8] = p->org[2] + right[2] + up[2];
2464 v3f[ 9] = p->org[0] + right[0] - up[0];
2465 v3f[10] = p->org[1] + right[1] - up[1];
2466 v3f[11] = p->org[2] + right[2] - up[2];
2467 t2f[0] = tex->s1;t2f[1] = tex->t2;
2468 t2f[2] = tex->s1;t2f[3] = tex->t1;
2469 t2f[4] = tex->s2;t2f[5] = tex->t1;
2470 t2f[6] = tex->s2;t2f[7] = tex->t2;
2472 case PARTICLE_SPARK:
2473 len = VectorLength(p->vel);
2474 VectorNormalize2(p->vel, up);
2475 lenfactor = p->stretch * 0.04 * len;
2476 if(lenfactor < size * 0.5)
2477 lenfactor = size * 0.5;
2478 VectorMA(p->org, -lenfactor, up, v);
2479 VectorMA(p->org, lenfactor, up, up2);
2480 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2481 t2f[0] = tex->s1;t2f[1] = tex->t2;
2482 t2f[2] = tex->s1;t2f[3] = tex->t1;
2483 t2f[4] = tex->s2;t2f[5] = tex->t1;
2484 t2f[6] = tex->s2;t2f[7] = tex->t2;
2486 case PARTICLE_VBEAM:
2487 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2488 VectorSubtract(p->vel, p->org, up);
2489 VectorNormalize(up);
2490 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2491 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2492 t2f[0] = tex->s2;t2f[1] = v[0];
2493 t2f[2] = tex->s1;t2f[3] = v[0];
2494 t2f[4] = tex->s1;t2f[5] = v[1];
2495 t2f[6] = tex->s2;t2f[7] = v[1];
2497 case PARTICLE_HBEAM:
2498 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2499 VectorSubtract(p->vel, p->org, up);
2500 VectorNormalize(up);
2501 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2502 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2503 t2f[0] = v[0];t2f[1] = tex->t1;
2504 t2f[2] = v[0];t2f[3] = tex->t2;
2505 t2f[4] = v[1];t2f[5] = tex->t2;
2506 t2f[6] = v[1];t2f[7] = tex->t1;
2511 // now render batches of particles based on blendmode and texture
2512 blendmode = PBLEND_INVALID;
2514 GL_LockArrays(0, numsurfaces*4);
2517 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2519 p = cl.particles + surfacelist[surfacelistindex];
2521 if (blendmode != p->blendmode)
2523 blendmode = p->blendmode;
2527 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2529 case PBLEND_INVALID:
2531 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2534 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2538 if (texture != particletexture[p->texnum].texture)
2540 texture = particletexture[p->texnum].texture;
2541 R_Mesh_TexBind(0, R_GetTexture(texture));
2544 // iterate until we find a change in settings
2545 batchstart = surfacelistindex++;
2546 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2548 p = cl.particles + surfacelist[surfacelistindex];
2549 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2553 batchcount = surfacelistindex - batchstart;
2554 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2556 GL_LockArrays(0, 0);
2559 void R_DrawParticles (void)
2562 int drawparticles = r_drawparticles.integer;
2563 float minparticledist;
2565 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2571 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2572 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2574 // LordHavoc: early out conditions
2575 if (!cl.num_particles)
2578 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2579 gravity = frametime * cl.movevars_gravity;
2580 dvel = 1+4*frametime;
2581 decalfade = frametime * 255 / cl_decals_fadetime.value;
2582 update = frametime > 0;
2583 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2584 drawdist2 = drawdist2*drawdist2;
2586 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2590 if (cl.free_particle > i)
2591 cl.free_particle = i;
2597 if (p->delayedspawn > cl.time)
2599 p->delayedspawn = 0;
2603 p->size += p->sizeincrease * frametime;
2604 p->alpha -= p->alphafade * frametime;
2606 if (p->alpha <= 0 || p->die <= cl.time)
2609 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2611 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2613 if (p->typeindex == pt_blood)
2614 p->size += frametime * 8;
2616 p->vel[2] -= p->gravity * gravity;
2617 f = 1.0f - min(p->liquidfriction * frametime, 1);
2618 VectorScale(p->vel, f, p->vel);
2622 p->vel[2] -= p->gravity * gravity;
2625 f = 1.0f - min(p->airfriction * frametime, 1);
2626 VectorScale(p->vel, f, p->vel);
2630 VectorCopy(p->org, oldorg);
2631 VectorMA(p->org, frametime, p->vel, p->org);
2632 if (p->bounce && cl.time >= p->delayedcollisions)
2634 trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), true, false, &hitent, false);
2635 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2636 // or if the trace hit something flagged as NOIMPACT
2637 // then remove the particle
2638 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2640 VectorCopy(trace.endpos, p->org);
2641 // react if the particle hit something
2642 if (trace.fraction < 1)
2644 VectorCopy(trace.endpos, p->org);
2646 if (p->staintexnum >= 0)
2648 // blood - splash on solid
2649 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2652 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2653 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2654 if (cl_decals.integer)
2656 // create a decal for the blood splat
2657 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->size * 2, p->alpha); // staincolor needs to be inverted for decals!
2662 if (p->typeindex == pt_blood)
2664 // blood - splash on solid
2665 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2667 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2669 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)));
2670 if (cl_decals.integer)
2672 // create a decal for the blood splat
2673 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * 2, p->alpha);
2678 else if (p->bounce < 0)
2680 // bounce -1 means remove on impact
2685 // anything else - bounce off solid
2686 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2687 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2688 if (DotProduct(p->vel, p->vel) < 0.03)
2689 VectorClear(p->vel);
2695 if (p->typeindex != pt_static)
2697 switch (p->typeindex)
2699 case pt_entityparticle:
2700 // particle that removes itself after one rendered frame
2707 a = CL_PointSuperContents(p->org);
2708 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2712 a = CL_PointSuperContents(p->org);
2713 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2717 a = CL_PointSuperContents(p->org);
2718 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2722 if (cl.time > p->time2)
2725 p->time2 = cl.time + (rand() & 3) * 0.1;
2726 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2727 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2729 a = CL_PointSuperContents(p->org);
2730 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2738 else if (p->delayedspawn)
2742 // don't render particles too close to the view (they chew fillrate)
2743 // also don't render particles behind the view (useless)
2744 // further checks to cull to the frustum would be too slow here
2745 switch(p->typeindex)
2748 // beams have no culling
2749 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2752 if(cl_particles_visculling.integer)
2753 if (!r_refdef.viewcache.world_novis)
2754 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2756 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2758 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2761 // anything else just has to be in front of the viewer and visible at this distance
2762 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2763 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2770 if (cl.free_particle > i)
2771 cl.free_particle = i;
2774 // reduce cl.num_particles if possible
2775 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2778 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2780 particle_t *oldparticles = cl.particles;
2781 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2782 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2783 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2784 Mem_Free(oldparticles);