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 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
128 static int particlepalette[256];
130 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
131 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
132 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
133 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
134 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
135 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
136 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
137 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
138 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
139 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
140 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
141 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
142 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
143 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
144 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
145 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
146 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
147 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
148 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
149 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
150 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
151 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
152 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
153 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
154 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
155 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
156 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
157 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
158 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
159 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
160 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
161 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
164 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
165 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
166 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
168 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
170 // particletexture_t is a rectangle in the particlefonttexture
171 typedef struct particletexture_s
174 float s1, t1, s2, t2;
178 static rtexturepool_t *particletexturepool;
179 static rtexture_t *particlefonttexture;
180 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
181 skinframe_t *decalskinframe;
183 // texture numbers in particle font
184 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
185 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
186 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
187 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
188 static const int tex_rainsplash = 32;
189 static const int tex_particle = 63;
190 static const int tex_bubble = 62;
191 static const int tex_raindrop = 61;
192 static const int tex_beam = 60;
194 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
195 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
196 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
197 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
198 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
199 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
200 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
201 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
202 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
203 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
204 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
205 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
206 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
207 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
208 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
209 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
210 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
211 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
212 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
213 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
214 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
215 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
216 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
217 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
218 cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"};
219 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
220 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
221 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
224 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
230 particleeffectinfo_t *info = NULL;
231 const char *text = textstart;
233 effectinfoindex = -1;
234 for (linenumber = 1;;linenumber++)
237 for (arrayindex = 0;arrayindex < 16;arrayindex++)
238 argv[arrayindex][0] = 0;
241 if (!COM_ParseToken_Simple(&text, true, false))
243 if (!strcmp(com_token, "\n"))
247 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
253 #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;}
254 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
255 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
256 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
257 #define readfloat(var) checkparms(2);var = atof(argv[1])
258 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
259 if (!strcmp(argv[0], "effect"))
264 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
266 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
269 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
271 if (particleeffectname[effectnameindex][0])
273 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
278 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
282 // if we run out of names, abort
283 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
285 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
288 info = particleeffectinfo + effectinfoindex;
289 info->effectnameindex = effectnameindex;
290 info->particletype = pt_alphastatic;
291 info->blendmode = particletype[info->particletype].blendmode;
292 info->orientation = particletype[info->particletype].orientation;
293 info->tex[0] = tex_particle;
294 info->tex[1] = tex_particle;
295 info->color[0] = 0xFFFFFF;
296 info->color[1] = 0xFFFFFF;
300 info->alpha[1] = 256;
301 info->alpha[2] = 256;
302 info->time[0] = 9999;
303 info->time[1] = 9999;
304 VectorSet(info->lightcolor, 1, 1, 1);
305 info->lightshadow = true;
306 info->lighttime = 9999;
307 info->stretchfactor = 1;
308 info->staincolor[0] = (unsigned int)-1;
309 info->staincolor[1] = (unsigned int)-1;
310 info->staintex[0] = -1;
311 info->staintex[1] = -1;
313 else if (info == NULL)
315 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
318 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
319 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
320 else if (!strcmp(argv[0], "type"))
323 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
324 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
325 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
326 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
327 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
328 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
329 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
330 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
331 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
332 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
333 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
334 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
335 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
336 info->blendmode = particletype[info->particletype].blendmode;
337 info->orientation = particletype[info->particletype].orientation;
339 else if (!strcmp(argv[0], "blend"))
342 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
343 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
344 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
345 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
347 else if (!strcmp(argv[0], "orientation"))
350 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
351 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
352 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
353 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
354 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
356 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
357 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
358 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
359 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
360 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
361 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
362 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
363 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
364 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
365 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
366 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
367 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
368 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
369 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
370 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
371 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
372 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
373 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
374 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
375 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
376 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
377 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
378 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
379 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
380 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
381 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
382 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
383 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
385 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
394 int CL_ParticleEffectIndexForName(const char *name)
397 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
398 if (!strcmp(particleeffectname[i], name))
403 const char *CL_ParticleEffectNameForIndex(int i)
405 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
407 return particleeffectname[i];
410 // MUST match effectnameindex_t in client.h
411 static const char *standardeffectnames[EFFECT_TOTAL] =
435 "TE_TEI_BIGEXPLOSION",
451 void CL_Particles_LoadEffectInfo(void)
454 unsigned char *filedata;
455 fs_offset_t filesize;
456 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
457 memset(particleeffectname, 0, sizeof(particleeffectname));
458 for (i = 0;i < EFFECT_TOTAL;i++)
459 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
460 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
463 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
473 void CL_ReadPointFile_f (void);
474 void CL_Particles_Init (void)
476 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)");
477 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
479 Cvar_RegisterVariable (&cl_particles);
480 Cvar_RegisterVariable (&cl_particles_quality);
481 Cvar_RegisterVariable (&cl_particles_alpha);
482 Cvar_RegisterVariable (&cl_particles_size);
483 Cvar_RegisterVariable (&cl_particles_quake);
484 Cvar_RegisterVariable (&cl_particles_blood);
485 Cvar_RegisterVariable (&cl_particles_blood_alpha);
486 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
487 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
488 Cvar_RegisterVariable (&cl_particles_explosions_shell);
489 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
490 Cvar_RegisterVariable (&cl_particles_rain);
491 Cvar_RegisterVariable (&cl_particles_snow);
492 Cvar_RegisterVariable (&cl_particles_smoke);
493 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
494 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
495 Cvar_RegisterVariable (&cl_particles_sparks);
496 Cvar_RegisterVariable (&cl_particles_bubbles);
497 Cvar_RegisterVariable (&cl_particles_visculling);
498 Cvar_RegisterVariable (&cl_decals);
499 Cvar_RegisterVariable (&cl_decals_visculling);
500 Cvar_RegisterVariable (&cl_decals_time);
501 Cvar_RegisterVariable (&cl_decals_fadetime);
502 Cvar_RegisterVariable (&cl_decals_newsystem);
503 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
504 Cvar_RegisterVariable (&cl_decals_models);
505 Cvar_RegisterVariable (&cl_decals_bias);
506 Cvar_RegisterVariable (&cl_decals_max);
509 void CL_Particles_Shutdown (void)
513 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
514 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
516 // list of all 26 parameters:
517 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
518 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
519 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
520 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
521 // palpha - opacity of particle as 0-255 (can be more than 255)
522 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
523 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
524 // pgravity - how much effect gravity has on the particle (0-1)
525 // 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
526 // px,py,pz - starting origin of particle
527 // pvx,pvy,pvz - starting velocity of particle
528 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
529 // blendmode - one of the PBLEND_ values
530 // orientation - one of the PARTICLE_ values
531 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
532 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
533 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)
538 if (!cl_particles.integer)
540 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
541 if (cl.free_particle >= cl.max_particles)
544 lifetime = palpha / min(1, palphafade);
545 part = &cl.particles[cl.free_particle++];
546 if (cl.num_particles < cl.free_particle)
547 cl.num_particles = cl.free_particle;
548 memset(part, 0, sizeof(*part));
549 part->typeindex = ptypeindex;
550 part->blendmode = blendmode;
551 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
553 particletexture_t *tex = &particletexture[ptex];
554 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
555 part->orientation = PARTICLE_VBEAM;
557 part->orientation = PARTICLE_HBEAM;
560 part->orientation = orientation;
561 l2 = (int)lhrandom(0.5, 256.5);
563 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
564 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
565 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
566 part->staintexnum = staintex;
567 if(staincolor1 >= 0 && staincolor2 >= 0)
569 l2 = (int)lhrandom(0.5, 256.5);
571 if(blendmode == PBLEND_INVMOD)
573 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
574 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
575 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
579 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
580 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
581 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
583 if(r > 0xFF) r = 0xFF;
584 if(g > 0xFF) g = 0xFF;
585 if(b > 0xFF) b = 0xFF;
589 r = part->color[0]; // -1 is shorthand for stain = particle color
593 part->staincolor = r * 65536 + g * 256 + b;
596 part->sizeincrease = psizeincrease;
597 part->alpha = palpha;
598 part->alphafade = palphafade;
599 part->gravity = pgravity;
600 part->bounce = pbounce;
601 part->stretch = stretch;
603 part->org[0] = px + originjitter * v[0];
604 part->org[1] = py + originjitter * v[1];
605 part->org[2] = pz + originjitter * v[2];
606 part->vel[0] = pvx + velocityjitter * v[0];
607 part->vel[1] = pvy + velocityjitter * v[1];
608 part->vel[2] = pvz + velocityjitter * v[2];
610 part->airfriction = pairfriction;
611 part->liquidfriction = pliquidfriction;
612 part->die = cl.time + lifetime;
613 part->delayedcollisions = 0;
614 part->qualityreduction = pqualityreduction;
615 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
616 if (part->typeindex == pt_rain)
620 float lifetime = part->die - cl.time;
623 // turn raindrop into simple spark and create delayedspawn splash effect
624 part->typeindex = pt_spark;
626 VectorMA(part->org, lifetime, part->vel, endvec);
627 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
628 part->die = cl.time + lifetime * trace.fraction;
629 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);
632 part2->delayedspawn = part->die;
633 part2->die += part->die - cl.time;
634 for (i = rand() & 7;i < 10;i++)
636 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);
639 part2->delayedspawn = part->die;
640 part2->die += part->die - cl.time;
645 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
647 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
650 VectorMA(part->org, lifetime, part->vel, endvec);
651 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
652 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
658 static void CL_ImmediateBloodStain(particle_t *part)
663 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
664 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
666 VectorCopy(part->vel, v);
668 staintex = part->staintexnum;
669 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);
672 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
673 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
675 VectorCopy(part->vel, v);
677 staintex = tex_blooddecal[rand()&7];
678 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);
682 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
686 entity_render_t *ent = &cl.entities[hitent].render;
687 unsigned char color[3];
688 if (!cl_decals.integer)
690 if (!ent->allowdecals)
693 l2 = (int)lhrandom(0.5, 256.5);
695 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
696 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
697 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
699 if (cl_decals_newsystem.integer)
701 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);
705 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
706 if (cl.free_decal >= cl.max_decals)
708 decal = &cl.decals[cl.free_decal++];
709 if (cl.num_decals < cl.free_decal)
710 cl.num_decals = cl.free_decal;
711 memset(decal, 0, sizeof(*decal));
712 decal->decalsequence = cl.decalsequence++;
713 decal->typeindex = pt_decal;
714 decal->texnum = texnum;
715 VectorMA(org, cl_decals_bias.value, normal, decal->org);
716 VectorCopy(normal, decal->normal);
718 decal->alpha = alpha;
719 decal->time2 = cl.time;
720 decal->color[0] = color[0];
721 decal->color[1] = color[1];
722 decal->color[2] = color[2];
723 decal->owner = hitent;
724 decal->clusterindex = -1000; // no vis culling unless we're sure
727 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
728 decal->ownermodel = cl.entities[decal->owner].render.model;
729 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
730 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
734 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
736 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
738 decal->clusterindex = leaf->clusterindex;
743 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
746 float bestfrac, bestorg[3], bestnormal[3];
748 int besthitent = 0, hitent;
751 for (i = 0;i < 32;i++)
754 VectorMA(org, maxdist, org2, org2);
755 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
756 // take the closest trace result that doesn't end up hitting a NOMARKS
757 // surface (sky for example)
758 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
760 bestfrac = trace.fraction;
762 VectorCopy(trace.endpos, bestorg);
763 VectorCopy(trace.plane.normal, bestnormal);
767 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
770 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
771 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
772 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)
775 matrix4x4_t tempmatrix;
777 VectorLerp(originmins, 0.5, originmaxs, center);
778 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
779 if (effectnameindex == EFFECT_SVC_PARTICLE)
781 if (cl_particles.integer)
783 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
785 CL_ParticleExplosion(center);
786 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
787 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
790 count *= cl_particles_quality.value;
791 for (;count > 0;count--)
793 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
794 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);
799 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
800 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
801 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
802 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
803 else if (effectnameindex == EFFECT_TE_SPIKE)
805 if (cl_particles_bulletimpacts.integer)
807 if (cl_particles_quake.integer)
809 if (cl_particles_smoke.integer)
810 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
814 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
815 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
816 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);
820 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
821 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
823 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
825 if (cl_particles_bulletimpacts.integer)
827 if (cl_particles_quake.integer)
829 if (cl_particles_smoke.integer)
830 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
834 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
835 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
836 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);
840 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
841 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
842 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);
844 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
846 if (cl_particles_bulletimpacts.integer)
848 if (cl_particles_quake.integer)
850 if (cl_particles_smoke.integer)
851 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
855 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
856 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
857 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);
861 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
862 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
864 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
866 if (cl_particles_bulletimpacts.integer)
868 if (cl_particles_quake.integer)
870 if (cl_particles_smoke.integer)
871 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
875 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
876 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
877 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);
881 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
882 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
883 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);
885 else if (effectnameindex == EFFECT_TE_BLOOD)
887 if (!cl_particles_blood.integer)
889 if (cl_particles_quake.integer)
890 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
893 static double bloodaccumulator = 0;
894 qboolean immediatebloodstain = true;
895 //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);
896 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
897 for (;bloodaccumulator > 0;bloodaccumulator--)
899 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);
900 if (immediatebloodstain && part)
902 immediatebloodstain = false;
903 CL_ImmediateBloodStain(part);
908 else if (effectnameindex == EFFECT_TE_SPARK)
909 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
910 else if (effectnameindex == EFFECT_TE_PLASMABURN)
912 // plasma scorch mark
913 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
914 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
915 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
917 else if (effectnameindex == EFFECT_TE_GUNSHOT)
919 if (cl_particles_bulletimpacts.integer)
921 if (cl_particles_quake.integer)
922 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
925 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
926 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
927 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);
931 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
932 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
934 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
936 if (cl_particles_bulletimpacts.integer)
938 if (cl_particles_quake.integer)
939 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
942 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
943 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
944 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);
948 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
949 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
950 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);
952 else if (effectnameindex == EFFECT_TE_EXPLOSION)
954 CL_ParticleExplosion(center);
955 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);
957 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
959 CL_ParticleExplosion(center);
960 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);
962 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
964 if (cl_particles_quake.integer)
967 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
970 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);
972 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);
976 CL_ParticleExplosion(center);
977 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);
979 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
980 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);
981 else if (effectnameindex == EFFECT_TE_FLAMEJET)
983 count *= cl_particles_quality.value;
985 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);
987 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
989 float i, j, inc, vel;
992 inc = 8 / cl_particles_quality.value;
993 for (i = -128;i < 128;i += inc)
995 for (j = -128;j < 128;j += inc)
997 dir[0] = j + lhrandom(0, inc);
998 dir[1] = i + lhrandom(0, inc);
1000 org[0] = center[0] + dir[0];
1001 org[1] = center[1] + dir[1];
1002 org[2] = center[2] + lhrandom(0, 64);
1003 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1004 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);
1008 else if (effectnameindex == EFFECT_TE_TELEPORT)
1010 float i, j, k, inc, vel;
1013 if (cl_particles_quake.integer)
1014 inc = 4 / cl_particles_quality.value;
1016 inc = 8 / cl_particles_quality.value;
1017 for (i = -16;i < 16;i += inc)
1019 for (j = -16;j < 16;j += inc)
1021 for (k = -24;k < 32;k += inc)
1023 VectorSet(dir, i*8, j*8, k*8);
1024 VectorNormalize(dir);
1025 vel = lhrandom(50, 113);
1026 if (cl_particles_quake.integer)
1027 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);
1029 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);
1033 if (!cl_particles_quake.integer)
1034 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);
1035 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);
1037 else if (effectnameindex == EFFECT_TE_TEI_G3)
1038 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);
1039 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1041 if (cl_particles_smoke.integer)
1043 count *= 0.25f * cl_particles_quality.value;
1045 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);
1048 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1050 CL_ParticleExplosion(center);
1051 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);
1053 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1056 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1057 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1058 if (cl_particles_smoke.integer)
1059 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1060 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);
1061 if (cl_particles_sparks.integer)
1062 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1063 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);
1064 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);
1066 else if (effectnameindex == EFFECT_EF_FLAME)
1068 count *= 300 * cl_particles_quality.value;
1070 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);
1071 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);
1073 else if (effectnameindex == EFFECT_EF_STARDUST)
1075 count *= 200 * cl_particles_quality.value;
1077 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);
1078 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);
1080 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1084 int smoke, blood, bubbles, r, color;
1086 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1089 Vector4Set(light, 0, 0, 0, 0);
1091 if (effectnameindex == EFFECT_TR_ROCKET)
1092 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1093 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1095 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1096 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1098 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1100 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1101 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1105 matrix4x4_t tempmatrix;
1106 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1107 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);
1108 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1112 if (!spawnparticles)
1115 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1118 VectorSubtract(originmaxs, originmins, dir);
1119 len = VectorNormalizeLength(dir);
1122 dec = -ent->persistent.trail_time;
1123 ent->persistent.trail_time += len;
1124 if (ent->persistent.trail_time < 0.01f)
1127 // if we skip out, leave it reset
1128 ent->persistent.trail_time = 0.0f;
1133 // advance into this frame to reach the first puff location
1134 VectorMA(originmins, dec, dir, pos);
1137 smoke = cl_particles.integer && cl_particles_smoke.integer;
1138 blood = cl_particles.integer && cl_particles_blood.integer;
1139 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1140 qd = 1.0f / cl_particles_quality.value;
1147 if (effectnameindex == EFFECT_TR_BLOOD)
1149 if (cl_particles_quake.integer)
1151 color = particlepalette[67 + (rand()&3)];
1152 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);
1157 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);
1160 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1162 if (cl_particles_quake.integer)
1165 color = particlepalette[67 + (rand()&3)];
1166 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);
1171 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);
1177 if (effectnameindex == EFFECT_TR_ROCKET)
1179 if (cl_particles_quake.integer)
1182 color = particlepalette[ramp3[r]];
1183 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);
1187 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);
1188 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);
1191 else if (effectnameindex == EFFECT_TR_GRENADE)
1193 if (cl_particles_quake.integer)
1196 color = particlepalette[ramp3[r]];
1197 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);
1201 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);
1204 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1206 if (cl_particles_quake.integer)
1209 color = particlepalette[52 + (rand()&7)];
1210 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 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);
1213 else if (gamemode == GAME_GOODVSBAD2)
1216 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);
1220 color = particlepalette[20 + (rand()&7)];
1221 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);
1224 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1226 if (cl_particles_quake.integer)
1229 color = particlepalette[230 + (rand()&7)];
1230 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);
1231 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);
1235 color = particlepalette[226 + (rand()&7)];
1236 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);
1239 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1241 if (cl_particles_quake.integer)
1243 color = particlepalette[152 + (rand()&3)];
1244 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);
1246 else if (gamemode == GAME_GOODVSBAD2)
1249 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);
1251 else if (gamemode == GAME_PRYDON)
1254 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);
1257 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);
1259 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1262 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);
1264 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1267 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);
1269 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1270 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);
1274 if (effectnameindex == EFFECT_TR_ROCKET)
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);
1276 else if (effectnameindex == EFFECT_TR_GRENADE)
1277 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);
1279 // advance to next time and position
1282 VectorMA (pos, dec, dir, pos);
1285 ent->persistent.trail_time = len;
1287 else if (developer.integer >= 1)
1288 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1291 // this is also called on point effects with spawndlight = true and
1292 // spawnparticles = true
1293 // it is called CL_ParticleTrail because most code does not want to supply
1294 // these parameters, only trail handling does
1295 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)
1298 qboolean found = false;
1299 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1301 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1302 return; // no such effect
1304 VectorLerp(originmins, 0.5, originmaxs, center);
1305 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1307 int effectinfoindex;
1310 particleeffectinfo_t *info;
1312 vec3_t centervelocity;
1318 qboolean underwater;
1319 qboolean immediatebloodstain;
1321 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1322 VectorLerp(originmins, 0.5, originmaxs, center);
1323 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1324 supercontents = CL_PointSuperContents(center);
1325 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1326 VectorSubtract(originmaxs, originmins, traildir);
1327 traillen = VectorLength(traildir);
1328 VectorNormalize(traildir);
1329 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1331 if (info->effectnameindex == effectnameindex)
1334 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1336 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1339 // spawn a dlight if requested
1340 if (info->lightradiusstart > 0 && spawndlight)
1342 matrix4x4_t tempmatrix;
1343 if (info->trailspacing > 0)
1344 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1346 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1347 if (info->lighttime > 0 && info->lightradiusfade > 0)
1349 // light flash (explosion, etc)
1350 // called when effect starts
1351 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);
1356 // called by CL_LinkNetworkEntity
1357 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1358 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);
1359 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1363 if (!spawnparticles)
1368 if (info->tex[1] > info->tex[0])
1370 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1371 tex = min(tex, info->tex[1] - 1);
1373 if(info->staintex[0] < 0)
1374 staintex = info->staintex[0];
1377 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1378 staintex = min(staintex, info->staintex[1] - 1);
1380 if (info->particletype == pt_decal)
1381 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]);
1382 else if (info->orientation == PARTICLE_HBEAM)
1383 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);
1386 if (!cl_particles.integer)
1388 switch (info->particletype)
1390 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1391 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1392 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1393 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1394 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1395 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1398 VectorCopy(originmins, trailpos);
1399 if (info->trailspacing > 0)
1401 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1402 trailstep = info->trailspacing / cl_particles_quality.value;
1403 immediatebloodstain = false;
1407 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1409 immediatebloodstain = info->particletype == pt_blood || staintex;
1411 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1412 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1414 if (info->tex[1] > info->tex[0])
1416 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1417 tex = min(tex, info->tex[1] - 1);
1421 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1422 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1423 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1426 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);
1427 if (immediatebloodstain && part)
1429 immediatebloodstain = false;
1430 CL_ImmediateBloodStain(part);
1433 VectorMA(trailpos, trailstep, traildir, trailpos);
1440 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1443 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)
1445 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1453 void CL_EntityParticles (const entity_t *ent)
1456 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1457 static vec3_t avelocities[NUMVERTEXNORMALS];
1458 if (!cl_particles.integer) return;
1459 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1461 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1463 if (!avelocities[0][0])
1464 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1465 avelocities[0][i] = lhrandom(0, 2.55);
1467 for (i = 0;i < NUMVERTEXNORMALS;i++)
1469 yaw = cl.time * avelocities[i][0];
1470 pitch = cl.time * avelocities[i][1];
1471 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1472 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1473 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1474 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);
1479 void CL_ReadPointFile_f (void)
1481 vec3_t org, leakorg;
1483 char *pointfile = NULL, *pointfilepos, *t, tchar;
1484 char name[MAX_OSPATH];
1489 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1490 strlcat (name, ".pts", sizeof (name));
1491 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1494 Con_Printf("Could not open %s\n", name);
1498 Con_Printf("Reading %s...\n", name);
1499 VectorClear(leakorg);
1502 pointfilepos = pointfile;
1503 while (*pointfilepos)
1505 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1510 while (*t && *t != '\n' && *t != '\r')
1514 #if _MSC_VER >= 1400
1515 #define sscanf sscanf_s
1517 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1523 VectorCopy(org, leakorg);
1526 if (cl.num_particles < cl.max_particles - 3)
1529 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);
1532 Mem_Free(pointfile);
1533 VectorCopy(leakorg, org);
1534 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1536 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);
1537 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);
1538 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);
1543 CL_ParseParticleEffect
1545 Parse an effect out of the server message
1548 void CL_ParseParticleEffect (void)
1551 int i, count, msgcount, color;
1553 MSG_ReadVector(org, cls.protocol);
1554 for (i=0 ; i<3 ; i++)
1555 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1556 msgcount = MSG_ReadByte ();
1557 color = MSG_ReadByte ();
1559 if (msgcount == 255)
1564 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1569 CL_ParticleExplosion
1573 void CL_ParticleExplosion (const vec3_t org)
1579 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1580 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1582 if (cl_particles_quake.integer)
1584 for (i = 0;i < 1024;i++)
1590 color = particlepalette[ramp1[r]];
1591 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);
1595 color = particlepalette[ramp2[r]];
1596 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);
1602 i = CL_PointSuperContents(org);
1603 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1605 if (cl_particles.integer && cl_particles_bubbles.integer)
1606 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1607 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);
1611 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1613 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1617 for (k = 0;k < 16;k++)
1620 VectorMA(org, 128, v2, v);
1621 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1622 if (trace.fraction >= 0.1)
1625 VectorSubtract(trace.endpos, org, v2);
1626 VectorScale(v2, 2.0f, v2);
1627 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);
1633 if (cl_particles_explosions_shell.integer)
1634 R_NewExplosion(org);
1639 CL_ParticleExplosion2
1643 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1646 if (!cl_particles.integer) return;
1648 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1650 k = particlepalette[colorStart + (i % colorLength)];
1651 if (cl_particles_quake.integer)
1652 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);
1654 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);
1658 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1660 if (cl_particles_sparks.integer)
1662 sparkcount *= cl_particles_quality.value;
1663 while(sparkcount-- > 0)
1664 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);
1668 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1670 if (cl_particles_smoke.integer)
1672 smokecount *= cl_particles_quality.value;
1673 while(smokecount-- > 0)
1674 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);
1678 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)
1681 if (!cl_particles.integer) return;
1683 count = (int)(count * cl_particles_quality.value);
1686 k = particlepalette[colorbase + (rand()&3)];
1687 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);
1691 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1694 float minz, maxz, lifetime = 30;
1695 if (!cl_particles.integer) return;
1696 if (dir[2] < 0) // falling
1698 minz = maxs[2] + dir[2] * 0.1;
1701 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1706 maxz = maxs[2] + dir[2] * 0.1;
1708 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1711 count = (int)(count * cl_particles_quality.value);
1716 if (!cl_particles_rain.integer) break;
1717 count *= 4; // ick, this should be in the mod or maps?
1721 k = particlepalette[colorbase + (rand()&3)];
1722 if (gamemode == GAME_GOODVSBAD2)
1723 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);
1725 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);
1729 if (!cl_particles_snow.integer) break;
1732 k = particlepalette[colorbase + (rand()&3)];
1733 if (gamemode == GAME_GOODVSBAD2)
1734 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);
1736 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);
1740 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1744 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1745 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1746 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1747 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1749 #define PARTICLETEXTURESIZE 64
1750 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1752 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1756 dz = 1 - (dx*dx+dy*dy);
1757 if (dz > 0) // it does hit the sphere
1761 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1762 VectorNormalize(normal);
1763 dot = DotProduct(normal, light);
1764 if (dot > 0.5) // interior reflection
1765 f += ((dot * 2) - 1);
1766 else if (dot < -0.5) // exterior reflection
1767 f += ((dot * -2) - 1);
1769 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1770 VectorNormalize(normal);
1771 dot = DotProduct(normal, light);
1772 if (dot > 0.5) // interior reflection
1773 f += ((dot * 2) - 1);
1774 else if (dot < -0.5) // exterior reflection
1775 f += ((dot * -2) - 1);
1777 f += 16; // just to give it a haze so you can see the outline
1778 f = bound(0, f, 255);
1779 return (unsigned char) f;
1785 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1786 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1788 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1789 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1790 *width = particlefontcellwidth;
1791 *height = particlefontcellheight;
1794 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1796 int basex, basey, w, h, y;
1797 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1798 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1799 Sys_Error("invalid particle texture size for autogenerating");
1800 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1801 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1804 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1807 float cx, cy, dx, dy, f, iradius;
1809 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1810 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1811 iradius = 1.0f / radius;
1812 alpha *= (1.0f / 255.0f);
1813 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1815 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1819 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1824 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1825 d[0] += (int)(f * (blue - d[0]));
1826 d[1] += (int)(f * (green - d[1]));
1827 d[2] += (int)(f * (red - d[2]));
1833 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1836 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1838 data[0] = bound(minb, data[0], maxb);
1839 data[1] = bound(ming, data[1], maxg);
1840 data[2] = bound(minr, data[2], maxr);
1844 void particletextureinvert(unsigned char *data)
1847 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1849 data[0] = 255 - data[0];
1850 data[1] = 255 - data[1];
1851 data[2] = 255 - data[2];
1855 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1856 static void R_InitBloodTextures (unsigned char *particletexturedata)
1859 unsigned char *data = Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4);
1862 for (i = 0;i < 8;i++)
1864 memset(data, 255, sizeof(data));
1865 for (k = 0;k < 24;k++)
1866 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1867 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1868 particletextureinvert(data);
1869 setuptex(tex_bloodparticle[i], data, particletexturedata);
1873 for (i = 0;i < 8;i++)
1875 memset(data, 255, sizeof(data));
1877 for (j = 1;j < 10;j++)
1878 for (k = min(j, m - 1);k < m;k++)
1879 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1880 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1881 particletextureinvert(data);
1882 setuptex(tex_blooddecal[i], data, particletexturedata);
1888 //uncomment this to make engine save out particle font to a tga file when run
1889 //#define DUMPPARTICLEFONT
1891 static void R_InitParticleTexture (void)
1893 int x, y, d, i, k, m;
1894 int basex, basey, w, h;
1898 fs_offset_t filesize;
1900 // a note: decals need to modulate (multiply) the background color to
1901 // properly darken it (stain), and they need to be able to alpha fade,
1902 // this is a very difficult challenge because it means fading to white
1903 // (no change to background) rather than black (darkening everything
1904 // behind the whole decal polygon), and to accomplish this the texture is
1905 // inverted (dark red blood on white background becomes brilliant cyan
1906 // and white on black background) so we can alpha fade it to black, then
1907 // we invert it again during the blendfunc to make it work...
1909 #ifndef DUMPPARTICLEFONT
1910 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
1913 particlefonttexture = decalskinframe->base;
1914 // TODO maybe allow custom grid size?
1915 particlefontwidth = image_width;
1916 particlefontheight = image_height;
1917 particlefontcellwidth = image_width / 8;
1918 particlefontcellheight = image_height / 8;
1919 particlefontcols = 8;
1920 particlefontrows = 8;
1925 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1926 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4);
1927 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1928 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1930 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1931 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1932 particlefontcols = 8;
1933 particlefontrows = 8;
1935 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1938 for (i = 0;i < 8;i++)
1940 memset(data, 255, sizeof(data));
1943 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1944 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1946 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1948 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1949 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1951 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1952 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1954 d = (int)(d * (1-(dx*dx+dy*dy)));
1955 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1956 d = bound(0, d, 255);
1957 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1964 setuptex(tex_smoke[i], data, particletexturedata);
1968 memset(data, 255, sizeof(data));
1969 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1971 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1972 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1974 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1975 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1976 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
1979 setuptex(tex_rainsplash, data, particletexturedata);
1982 memset(data, 255, sizeof(data));
1983 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1985 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1986 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1988 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1989 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1990 d = bound(0, d, 255);
1991 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1994 setuptex(tex_particle, data, particletexturedata);
1997 memset(data, 255, sizeof(data));
1998 light[0] = 1;light[1] = 1;light[2] = 1;
1999 VectorNormalize(light);
2000 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2002 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2003 // stretch upper half of bubble by +50% and shrink lower half by -50%
2004 // (this gives an elongated teardrop shape)
2006 dy = (dy - 0.5f) * 2.0f;
2008 dy = (dy - 0.5f) / 1.5f;
2009 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2011 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2012 // shrink bubble width to half
2014 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2017 setuptex(tex_raindrop, data, particletexturedata);
2020 memset(data, 255, sizeof(data));
2021 light[0] = 1;light[1] = 1;light[2] = 1;
2022 VectorNormalize(light);
2023 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2025 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2026 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2028 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2029 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2032 setuptex(tex_bubble, data, particletexturedata);
2034 // Blood particles and blood decals
2035 R_InitBloodTextures (particletexturedata);
2038 for (i = 0;i < 8;i++)
2040 memset(data, 255, sizeof(data));
2041 for (k = 0;k < 12;k++)
2042 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2043 for (k = 0;k < 3;k++)
2044 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2045 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2046 particletextureinvert(data);
2047 setuptex(tex_bulletdecal[i], data, particletexturedata);
2050 #ifdef DUMPPARTICLEFONT
2051 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2054 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2055 particlefonttexture = decalskinframe->base;
2057 Mem_Free(particletexturedata);
2062 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2064 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2065 particletexture[i].texture = particlefonttexture;
2066 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2067 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2068 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2069 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2072 #ifndef DUMPPARTICLEFONT
2073 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
2074 if (!particletexture[tex_beam].texture)
2077 unsigned char noise3[64][64], data2[64][16][4];
2079 fractalnoise(&noise3[0][0], 64, 4);
2081 for (y = 0;y < 64;y++)
2083 dy = (y - 0.5f*64) / (64*0.5f-1);
2084 for (x = 0;x < 16;x++)
2086 dx = (x - 0.5f*16) / (16*0.5f-2);
2087 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2088 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2089 data2[y][x][3] = 255;
2093 #ifdef DUMPPARTICLEFONT
2094 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2096 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2098 particletexture[tex_beam].s1 = 0;
2099 particletexture[tex_beam].t1 = 0;
2100 particletexture[tex_beam].s2 = 1;
2101 particletexture[tex_beam].t2 = 1;
2103 // now load an texcoord/texture override file
2104 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2111 if(!COM_ParseToken_Simple(&bufptr, true, false))
2113 if(!strcmp(com_token, "\n"))
2114 continue; // empty line
2115 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2116 particletexture[i].texture = particlefonttexture;
2118 if (!COM_ParseToken_Simple(&bufptr, true, false))
2120 if (!strcmp(com_token, "\n"))
2122 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2125 particletexture[i].s1 = atof(com_token);
2127 if (!COM_ParseToken_Simple(&bufptr, true, false))
2129 if (!strcmp(com_token, "\n"))
2131 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2134 particletexture[i].t1 = atof(com_token);
2136 if (!COM_ParseToken_Simple(&bufptr, true, false))
2138 if (!strcmp(com_token, "\n"))
2140 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2143 particletexture[i].s2 = atof(com_token);
2145 if (!COM_ParseToken_Simple(&bufptr, true, false))
2147 if (!strcmp(com_token, "\n"))
2149 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2152 particletexture[i].t2 = atof(com_token);
2158 static void r_part_start(void)
2161 // generate particlepalette for convenience from the main one
2162 for (i = 0;i < 256;i++)
2163 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2164 particletexturepool = R_AllocTexturePool();
2165 R_InitParticleTexture ();
2166 CL_Particles_LoadEffectInfo();
2169 static void r_part_shutdown(void)
2171 R_FreeTexturePool(&particletexturepool);
2174 static void r_part_newmap(void)
2177 R_SkinFrame_MarkUsed(decalskinframe);
2178 CL_Particles_LoadEffectInfo();
2181 #define BATCHSIZE 256
2182 unsigned short particle_elements[BATCHSIZE*6];
2183 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2185 void R_Particles_Init (void)
2188 for (i = 0;i < BATCHSIZE;i++)
2190 particle_elements[i*6+0] = i*4+0;
2191 particle_elements[i*6+1] = i*4+1;
2192 particle_elements[i*6+2] = i*4+2;
2193 particle_elements[i*6+3] = i*4+0;
2194 particle_elements[i*6+4] = i*4+2;
2195 particle_elements[i*6+5] = i*4+3;
2198 Cvar_RegisterVariable(&r_drawparticles);
2199 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2200 Cvar_RegisterVariable(&r_drawdecals);
2201 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2202 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2205 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2207 int surfacelistindex;
2209 float *v3f, *t2f, *c4f;
2210 particletexture_t *tex;
2211 float right[3], up[3], size, ca;
2212 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2213 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2215 RSurf_ActiveWorldEntity();
2217 r_refdef.stats.drawndecals += numsurfaces;
2218 R_Mesh_ResetTextureState();
2219 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2220 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2221 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2222 R_SetupGenericShader(true);
2223 GL_DepthMask(false);
2224 GL_DepthRange(0, 1);
2225 GL_PolygonOffset(0, 0);
2227 GL_CullFace(GL_NONE);
2229 // generate all the vertices at once
2230 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2232 d = cl.decals + surfacelist[surfacelistindex];
2235 c4f = particle_color4f + 16*surfacelistindex;
2236 ca = d->alpha * alphascale;
2237 if (r_refdef.fogenabled)
2238 ca *= RSurf_FogVertex(d->org);
2239 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2240 Vector4Copy(c4f, c4f + 4);
2241 Vector4Copy(c4f, c4f + 8);
2242 Vector4Copy(c4f, c4f + 12);
2244 // calculate vertex positions
2245 size = d->size * cl_particles_size.value;
2246 VectorVectors(d->normal, right, up);
2247 VectorScale(right, size, right);
2248 VectorScale(up, size, up);
2249 v3f = particle_vertex3f + 12*surfacelistindex;
2250 v3f[ 0] = d->org[0] - right[0] - up[0];
2251 v3f[ 1] = d->org[1] - right[1] - up[1];
2252 v3f[ 2] = d->org[2] - right[2] - up[2];
2253 v3f[ 3] = d->org[0] - right[0] + up[0];
2254 v3f[ 4] = d->org[1] - right[1] + up[1];
2255 v3f[ 5] = d->org[2] - right[2] + up[2];
2256 v3f[ 6] = d->org[0] + right[0] + up[0];
2257 v3f[ 7] = d->org[1] + right[1] + up[1];
2258 v3f[ 8] = d->org[2] + right[2] + up[2];
2259 v3f[ 9] = d->org[0] + right[0] - up[0];
2260 v3f[10] = d->org[1] + right[1] - up[1];
2261 v3f[11] = d->org[2] + right[2] - up[2];
2263 // calculate texcoords
2264 tex = &particletexture[d->texnum];
2265 t2f = particle_texcoord2f + 8*surfacelistindex;
2266 t2f[0] = tex->s1;t2f[1] = tex->t2;
2267 t2f[2] = tex->s1;t2f[3] = tex->t1;
2268 t2f[4] = tex->s2;t2f[5] = tex->t1;
2269 t2f[6] = tex->s2;t2f[7] = tex->t2;
2272 // now render the decals all at once
2273 // (this assumes they all use one particle font texture!)
2274 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2275 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2276 GL_LockArrays(0, numsurfaces*4);
2277 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2278 GL_LockArrays(0, 0);
2281 void R_DrawDecals (void)
2284 int drawdecals = r_drawdecals.integer;
2289 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2291 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2292 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2294 // LordHavoc: early out conditions
2298 decalfade = frametime * 256 / cl_decals_fadetime.value;
2299 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2300 drawdist2 = drawdist2*drawdist2;
2302 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2304 if (!decal->typeindex)
2307 if (killsequence - decal->decalsequence > 0)
2310 if (cl.time > decal->time2 + cl_decals_time.value)
2312 decal->alpha -= decalfade;
2313 if (decal->alpha <= 0)
2319 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2321 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2322 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2328 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2334 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))
2335 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2338 decal->typeindex = 0;
2339 if (cl.free_decal > i)
2343 // reduce cl.num_decals if possible
2344 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2347 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2349 decal_t *olddecals = cl.decals;
2350 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2351 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2352 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2353 Mem_Free(olddecals);
2356 r_refdef.stats.totaldecals = cl.num_decals;
2359 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2361 int surfacelistindex;
2362 int batchstart, batchcount;
2363 const particle_t *p;
2365 rtexture_t *texture;
2366 float *v3f, *t2f, *c4f;
2367 particletexture_t *tex;
2368 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2369 float ambient[3], diffuse[3], diffusenormal[3];
2370 vec4_t colormultiplier;
2372 RSurf_ActiveWorldEntity();
2374 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));
2376 r_refdef.stats.particles += numsurfaces;
2377 R_Mesh_ResetTextureState();
2378 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2379 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2380 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2381 R_SetupGenericShader(true);
2382 GL_DepthMask(false);
2383 GL_DepthRange(0, 1);
2384 GL_PolygonOffset(0, 0);
2386 GL_CullFace(GL_NONE);
2388 // first generate all the vertices at once
2389 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2391 p = cl.particles + surfacelist[surfacelistindex];
2393 blendmode = p->blendmode;
2395 c4f[0] = p->color[0] * colormultiplier[0];
2396 c4f[1] = p->color[1] * colormultiplier[1];
2397 c4f[2] = p->color[2] * colormultiplier[2];
2398 c4f[3] = p->alpha * colormultiplier[3];
2401 case PBLEND_INVALID:
2404 // additive and modulate can just fade out in fog (this is correct)
2405 if (r_refdef.fogenabled)
2406 c4f[3] *= RSurf_FogVertex(p->org);
2407 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2414 // note: lighting is not cheap!
2415 if (particletype[p->typeindex].lighting)
2417 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2418 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2419 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2420 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2422 // mix in the fog color
2423 if (r_refdef.fogenabled)
2425 fog = RSurf_FogVertex(p->org);
2427 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2428 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2429 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2433 // copy the color into the other three vertices
2434 Vector4Copy(c4f, c4f + 4);
2435 Vector4Copy(c4f, c4f + 8);
2436 Vector4Copy(c4f, c4f + 12);
2438 size = p->size * cl_particles_size.value;
2439 tex = &particletexture[p->texnum];
2440 switch(p->orientation)
2442 case PARTICLE_INVALID:
2443 case PARTICLE_BILLBOARD:
2444 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2445 VectorScale(r_refdef.view.up, size, up);
2446 v3f[ 0] = p->org[0] - right[0] - up[0];
2447 v3f[ 1] = p->org[1] - right[1] - up[1];
2448 v3f[ 2] = p->org[2] - right[2] - up[2];
2449 v3f[ 3] = p->org[0] - right[0] + up[0];
2450 v3f[ 4] = p->org[1] - right[1] + up[1];
2451 v3f[ 5] = p->org[2] - right[2] + up[2];
2452 v3f[ 6] = p->org[0] + right[0] + up[0];
2453 v3f[ 7] = p->org[1] + right[1] + up[1];
2454 v3f[ 8] = p->org[2] + right[2] + up[2];
2455 v3f[ 9] = p->org[0] + right[0] - up[0];
2456 v3f[10] = p->org[1] + right[1] - up[1];
2457 v3f[11] = p->org[2] + right[2] - up[2];
2458 t2f[0] = tex->s1;t2f[1] = tex->t2;
2459 t2f[2] = tex->s1;t2f[3] = tex->t1;
2460 t2f[4] = tex->s2;t2f[5] = tex->t1;
2461 t2f[6] = tex->s2;t2f[7] = tex->t2;
2463 case PARTICLE_ORIENTED_DOUBLESIDED:
2464 VectorVectors(p->vel, right, up);
2465 VectorScale(right, size * p->stretch, right);
2466 VectorScale(up, size, up);
2467 v3f[ 0] = p->org[0] - right[0] - up[0];
2468 v3f[ 1] = p->org[1] - right[1] - up[1];
2469 v3f[ 2] = p->org[2] - right[2] - up[2];
2470 v3f[ 3] = p->org[0] - right[0] + up[0];
2471 v3f[ 4] = p->org[1] - right[1] + up[1];
2472 v3f[ 5] = p->org[2] - right[2] + up[2];
2473 v3f[ 6] = p->org[0] + right[0] + up[0];
2474 v3f[ 7] = p->org[1] + right[1] + up[1];
2475 v3f[ 8] = p->org[2] + right[2] + up[2];
2476 v3f[ 9] = p->org[0] + right[0] - up[0];
2477 v3f[10] = p->org[1] + right[1] - up[1];
2478 v3f[11] = p->org[2] + right[2] - up[2];
2479 t2f[0] = tex->s1;t2f[1] = tex->t2;
2480 t2f[2] = tex->s1;t2f[3] = tex->t1;
2481 t2f[4] = tex->s2;t2f[5] = tex->t1;
2482 t2f[6] = tex->s2;t2f[7] = tex->t2;
2484 case PARTICLE_SPARK:
2485 len = VectorLength(p->vel);
2486 VectorNormalize2(p->vel, up);
2487 lenfactor = p->stretch * 0.04 * len;
2488 if(lenfactor < size * 0.5)
2489 lenfactor = size * 0.5;
2490 VectorMA(p->org, -lenfactor, up, v);
2491 VectorMA(p->org, lenfactor, up, up2);
2492 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2493 t2f[0] = tex->s1;t2f[1] = tex->t2;
2494 t2f[2] = tex->s1;t2f[3] = tex->t1;
2495 t2f[4] = tex->s2;t2f[5] = tex->t1;
2496 t2f[6] = tex->s2;t2f[7] = tex->t2;
2498 case PARTICLE_VBEAM:
2499 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2500 VectorSubtract(p->vel, p->org, up);
2501 VectorNormalize(up);
2502 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2503 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2504 t2f[0] = tex->s2;t2f[1] = v[0];
2505 t2f[2] = tex->s1;t2f[3] = v[0];
2506 t2f[4] = tex->s1;t2f[5] = v[1];
2507 t2f[6] = tex->s2;t2f[7] = v[1];
2509 case PARTICLE_HBEAM:
2510 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2511 VectorSubtract(p->vel, p->org, up);
2512 VectorNormalize(up);
2513 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2514 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2515 t2f[0] = v[0];t2f[1] = tex->t1;
2516 t2f[2] = v[0];t2f[3] = tex->t2;
2517 t2f[4] = v[1];t2f[5] = tex->t2;
2518 t2f[6] = v[1];t2f[7] = tex->t1;
2523 // now render batches of particles based on blendmode and texture
2524 blendmode = PBLEND_INVALID;
2526 GL_LockArrays(0, numsurfaces*4);
2529 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2531 p = cl.particles + surfacelist[surfacelistindex];
2533 if (blendmode != p->blendmode)
2535 blendmode = p->blendmode;
2539 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2541 case PBLEND_INVALID:
2543 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2546 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2550 if (texture != particletexture[p->texnum].texture)
2552 texture = particletexture[p->texnum].texture;
2553 R_Mesh_TexBind(0, R_GetTexture(texture));
2556 // iterate until we find a change in settings
2557 batchstart = surfacelistindex++;
2558 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2560 p = cl.particles + surfacelist[surfacelistindex];
2561 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2565 batchcount = surfacelistindex - batchstart;
2566 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2568 GL_LockArrays(0, 0);
2571 void R_DrawParticles (void)
2574 int drawparticles = r_drawparticles.integer;
2575 float minparticledist;
2577 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2583 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2584 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2586 // LordHavoc: early out conditions
2587 if (!cl.num_particles)
2590 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2591 gravity = frametime * cl.movevars_gravity;
2592 dvel = 1+4*frametime;
2593 decalfade = frametime * 255 / cl_decals_fadetime.value;
2594 update = frametime > 0;
2595 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2596 drawdist2 = drawdist2*drawdist2;
2598 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2602 if (cl.free_particle > i)
2603 cl.free_particle = i;
2609 if (p->delayedspawn > cl.time)
2611 p->delayedspawn = 0;
2615 p->size += p->sizeincrease * frametime;
2616 p->alpha -= p->alphafade * frametime;
2618 if (p->alpha <= 0 || p->die <= cl.time)
2621 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2623 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2625 if (p->typeindex == pt_blood)
2626 p->size += frametime * 8;
2628 p->vel[2] -= p->gravity * gravity;
2629 f = 1.0f - min(p->liquidfriction * frametime, 1);
2630 VectorScale(p->vel, f, p->vel);
2634 p->vel[2] -= p->gravity * gravity;
2637 f = 1.0f - min(p->airfriction * frametime, 1);
2638 VectorScale(p->vel, f, p->vel);
2642 VectorCopy(p->org, oldorg);
2643 VectorMA(p->org, frametime, p->vel, p->org);
2644 if (p->bounce && cl.time >= p->delayedcollisions)
2646 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);
2647 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2648 // or if the trace hit something flagged as NOIMPACT
2649 // then remove the particle
2650 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2652 VectorCopy(trace.endpos, p->org);
2653 // react if the particle hit something
2654 if (trace.fraction < 1)
2656 VectorCopy(trace.endpos, p->org);
2658 if (p->staintexnum >= 0)
2660 // blood - splash on solid
2661 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2664 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2665 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2666 if (cl_decals.integer)
2668 // create a decal for the blood splat
2669 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!
2674 if (p->typeindex == pt_blood)
2676 // blood - splash on solid
2677 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2679 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2681 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)));
2682 if (cl_decals.integer)
2684 // create a decal for the blood splat
2685 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);
2690 else if (p->bounce < 0)
2692 // bounce -1 means remove on impact
2697 // anything else - bounce off solid
2698 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2699 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2700 if (DotProduct(p->vel, p->vel) < 0.03)
2701 VectorClear(p->vel);
2707 if (p->typeindex != pt_static)
2709 switch (p->typeindex)
2711 case pt_entityparticle:
2712 // particle that removes itself after one rendered frame
2719 a = CL_PointSuperContents(p->org);
2720 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2724 a = CL_PointSuperContents(p->org);
2725 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2729 a = CL_PointSuperContents(p->org);
2730 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2734 if (cl.time > p->time2)
2737 p->time2 = cl.time + (rand() & 3) * 0.1;
2738 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2739 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2741 a = CL_PointSuperContents(p->org);
2742 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2750 else if (p->delayedspawn)
2754 // don't render particles too close to the view (they chew fillrate)
2755 // also don't render particles behind the view (useless)
2756 // further checks to cull to the frustum would be too slow here
2757 switch(p->typeindex)
2760 // beams have no culling
2761 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2764 if(cl_particles_visculling.integer)
2765 if (!r_refdef.viewcache.world_novis)
2766 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2768 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2770 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2773 // anything else just has to be in front of the viewer and visible at this distance
2774 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2775 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2782 if (cl.free_particle > i)
2783 cl.free_particle = i;
2786 // reduce cl.num_particles if possible
2787 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2790 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2792 particle_t *oldparticles = cl.particles;
2793 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2794 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2795 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2796 Mem_Free(oldparticles);