2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float velocityoffset[3];
105 float originjitter[3];
106 float velocityjitter[3];
107 float velocitymultiplier;
108 // an effect can also spawn a dlight
109 float lightradiusstart;
110 float lightradiusfade;
113 qboolean lightshadow;
115 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
118 particleeffectinfo_t;
120 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
123 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
125 static int particlepalette[256];
127 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
128 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
129 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
130 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
131 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
132 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
133 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
134 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
135 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
136 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
137 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
138 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
139 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
140 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
141 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
142 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
143 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
144 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
145 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
146 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
147 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
148 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
149 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
150 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
151 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
152 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
153 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
154 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
155 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
156 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
157 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
158 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
161 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
162 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
163 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
165 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
167 // particletexture_t is a rectangle in the particlefonttexture
168 typedef struct particletexture_s
171 float s1, t1, s2, t2;
175 static rtexturepool_t *particletexturepool;
176 static rtexture_t *particlefonttexture;
177 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
178 skinframe_t *decalskinframe;
180 // texture numbers in particle font
181 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
182 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
183 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
184 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
185 static const int tex_rainsplash = 32;
186 static const int tex_particle = 63;
187 static const int tex_bubble = 62;
188 static const int tex_raindrop = 61;
189 static const int tex_beam = 60;
191 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
192 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
193 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
194 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
195 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
196 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
197 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
198 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
199 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
200 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
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, const char *filename)
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("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, 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("%s:%i: too many effects!\n", filename, 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("%s:%i: too many effects!\n", filename, 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("%s:%i: command %s encountered before effect\n", filename, 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("%s:%i: unrecognized particle type %s\n", filename, 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("%s:%i: unrecognized blendmode %s\n", filename, 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("%s:%i: unrecognized orientation %s\n", filename, 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("%s:%i: skipping unknown command %s\n", filename, 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)
455 unsigned char *filedata;
456 fs_offset_t filesize;
457 char filename[MAX_QPATH];
458 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
459 memset(particleeffectname, 0, sizeof(particleeffectname));
460 for (i = 0;i < EFFECT_TOTAL;i++)
461 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
462 for (filepass = 0;;filepass++)
465 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
466 else if (filepass == 1)
467 dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
470 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
473 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
483 void CL_ReadPointFile_f (void);
484 void CL_Particles_Init (void)
486 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)");
487 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
489 Cvar_RegisterVariable (&cl_particles);
490 Cvar_RegisterVariable (&cl_particles_quality);
491 Cvar_RegisterVariable (&cl_particles_alpha);
492 Cvar_RegisterVariable (&cl_particles_size);
493 Cvar_RegisterVariable (&cl_particles_quake);
494 Cvar_RegisterVariable (&cl_particles_blood);
495 Cvar_RegisterVariable (&cl_particles_blood_alpha);
496 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
497 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
498 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
499 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
500 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
501 Cvar_RegisterVariable (&cl_particles_explosions_shell);
502 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
503 Cvar_RegisterVariable (&cl_particles_rain);
504 Cvar_RegisterVariable (&cl_particles_snow);
505 Cvar_RegisterVariable (&cl_particles_smoke);
506 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
507 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
508 Cvar_RegisterVariable (&cl_particles_sparks);
509 Cvar_RegisterVariable (&cl_particles_bubbles);
510 Cvar_RegisterVariable (&cl_particles_visculling);
511 Cvar_RegisterVariable (&cl_decals);
512 Cvar_RegisterVariable (&cl_decals_visculling);
513 Cvar_RegisterVariable (&cl_decals_time);
514 Cvar_RegisterVariable (&cl_decals_fadetime);
515 Cvar_RegisterVariable (&cl_decals_newsystem);
516 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
517 Cvar_RegisterVariable (&cl_decals_models);
518 Cvar_RegisterVariable (&cl_decals_bias);
519 Cvar_RegisterVariable (&cl_decals_max);
522 void CL_Particles_Shutdown (void)
526 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
527 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
529 // list of all 26 parameters:
530 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
531 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
532 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
533 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
534 // palpha - opacity of particle as 0-255 (can be more than 255)
535 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
536 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
537 // pgravity - how much effect gravity has on the particle (0-1)
538 // 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
539 // px,py,pz - starting origin of particle
540 // pvx,pvy,pvz - starting velocity of particle
541 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
542 // blendmode - one of the PBLEND_ values
543 // orientation - one of the PARTICLE_ values
544 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
545 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
546 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)
551 if (!cl_particles.integer)
553 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
554 if (cl.free_particle >= cl.max_particles)
557 lifetime = palpha / min(1, palphafade);
558 part = &cl.particles[cl.free_particle++];
559 if (cl.num_particles < cl.free_particle)
560 cl.num_particles = cl.free_particle;
561 memset(part, 0, sizeof(*part));
562 part->typeindex = ptypeindex;
563 part->blendmode = blendmode;
564 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
566 particletexture_t *tex = &particletexture[ptex];
567 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
568 part->orientation = PARTICLE_VBEAM;
570 part->orientation = PARTICLE_HBEAM;
573 part->orientation = orientation;
574 l2 = (int)lhrandom(0.5, 256.5);
576 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
577 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
578 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
579 part->staintexnum = staintex;
580 if(staincolor1 >= 0 && staincolor2 >= 0)
582 l2 = (int)lhrandom(0.5, 256.5);
584 if(blendmode == PBLEND_INVMOD)
586 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
587 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
588 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
592 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
593 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
594 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
596 if(r > 0xFF) r = 0xFF;
597 if(g > 0xFF) g = 0xFF;
598 if(b > 0xFF) b = 0xFF;
602 r = part->color[0]; // -1 is shorthand for stain = particle color
606 part->staincolor = r * 65536 + g * 256 + b;
609 part->sizeincrease = psizeincrease;
610 part->alpha = palpha;
611 part->alphafade = palphafade;
612 part->gravity = pgravity;
613 part->bounce = pbounce;
614 part->stretch = stretch;
616 part->org[0] = px + originjitter * v[0];
617 part->org[1] = py + originjitter * v[1];
618 part->org[2] = pz + originjitter * v[2];
619 part->vel[0] = pvx + velocityjitter * v[0];
620 part->vel[1] = pvy + velocityjitter * v[1];
621 part->vel[2] = pvz + velocityjitter * v[2];
623 part->airfriction = pairfriction;
624 part->liquidfriction = pliquidfriction;
625 part->die = cl.time + lifetime;
626 part->delayedcollisions = 0;
627 part->qualityreduction = pqualityreduction;
628 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
629 if (part->typeindex == pt_rain)
633 float lifetime = part->die - cl.time;
636 // turn raindrop into simple spark and create delayedspawn splash effect
637 part->typeindex = pt_spark;
639 VectorMA(part->org, lifetime, part->vel, endvec);
640 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
641 part->die = cl.time + lifetime * trace.fraction;
642 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);
645 part2->delayedspawn = part->die;
646 part2->die += part->die - cl.time;
647 for (i = rand() & 7;i < 10;i++)
649 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);
652 part2->delayedspawn = part->die;
653 part2->die += part->die - cl.time;
658 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
660 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
663 VectorMA(part->org, lifetime, part->vel, endvec);
664 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
665 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
671 static void CL_ImmediateBloodStain(particle_t *part)
676 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
677 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
679 VectorCopy(part->vel, v);
681 staintex = part->staintexnum;
682 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);
685 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
686 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
688 VectorCopy(part->vel, v);
690 staintex = tex_blooddecal[rand()&7];
691 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);
695 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
699 entity_render_t *ent = &cl.entities[hitent].render;
700 unsigned char color[3];
701 if (!cl_decals.integer)
703 if (!ent->allowdecals)
706 l2 = (int)lhrandom(0.5, 256.5);
708 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
709 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
710 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
712 if (cl_decals_newsystem.integer)
714 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);
718 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
719 if (cl.free_decal >= cl.max_decals)
721 decal = &cl.decals[cl.free_decal++];
722 if (cl.num_decals < cl.free_decal)
723 cl.num_decals = cl.free_decal;
724 memset(decal, 0, sizeof(*decal));
725 decal->decalsequence = cl.decalsequence++;
726 decal->typeindex = pt_decal;
727 decal->texnum = texnum;
728 VectorMA(org, cl_decals_bias.value, normal, decal->org);
729 VectorCopy(normal, decal->normal);
731 decal->alpha = alpha;
732 decal->time2 = cl.time;
733 decal->color[0] = color[0];
734 decal->color[1] = color[1];
735 decal->color[2] = color[2];
736 decal->owner = hitent;
737 decal->clusterindex = -1000; // no vis culling unless we're sure
740 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
741 decal->ownermodel = cl.entities[decal->owner].render.model;
742 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
743 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
747 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
749 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
751 decal->clusterindex = leaf->clusterindex;
756 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
759 float bestfrac, bestorg[3], bestnormal[3];
761 int besthitent = 0, hitent;
764 for (i = 0;i < 32;i++)
767 VectorMA(org, maxdist, org2, org2);
768 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
769 // take the closest trace result that doesn't end up hitting a NOMARKS
770 // surface (sky for example)
771 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
773 bestfrac = trace.fraction;
775 VectorCopy(trace.endpos, bestorg);
776 VectorCopy(trace.plane.normal, bestnormal);
780 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
783 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
784 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
785 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)
788 matrix4x4_t tempmatrix;
790 VectorLerp(originmins, 0.5, originmaxs, center);
791 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
792 if (effectnameindex == EFFECT_SVC_PARTICLE)
794 if (cl_particles.integer)
796 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
798 CL_ParticleExplosion(center);
799 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
800 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
803 count *= cl_particles_quality.value;
804 for (;count > 0;count--)
806 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
807 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);
812 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
813 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
814 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
815 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
816 else if (effectnameindex == EFFECT_TE_SPIKE)
818 if (cl_particles_bulletimpacts.integer)
820 if (cl_particles_quake.integer)
822 if (cl_particles_smoke.integer)
823 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
827 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
828 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
829 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);
833 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
834 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
836 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
838 if (cl_particles_bulletimpacts.integer)
840 if (cl_particles_quake.integer)
842 if (cl_particles_smoke.integer)
843 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
847 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
848 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
849 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);
853 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
854 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
855 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);
857 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
859 if (cl_particles_bulletimpacts.integer)
861 if (cl_particles_quake.integer)
863 if (cl_particles_smoke.integer)
864 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
868 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
869 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
870 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);
874 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
875 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
877 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
879 if (cl_particles_bulletimpacts.integer)
881 if (cl_particles_quake.integer)
883 if (cl_particles_smoke.integer)
884 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
888 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
889 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
890 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);
894 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
895 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
896 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);
898 else if (effectnameindex == EFFECT_TE_BLOOD)
900 if (!cl_particles_blood.integer)
902 if (cl_particles_quake.integer)
903 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
906 static double bloodaccumulator = 0;
907 qboolean immediatebloodstain = true;
908 //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);
909 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
910 for (;bloodaccumulator > 0;bloodaccumulator--)
912 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);
913 if (immediatebloodstain && part)
915 immediatebloodstain = false;
916 CL_ImmediateBloodStain(part);
921 else if (effectnameindex == EFFECT_TE_SPARK)
922 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
923 else if (effectnameindex == EFFECT_TE_PLASMABURN)
925 // plasma scorch mark
926 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
927 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
928 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
930 else if (effectnameindex == EFFECT_TE_GUNSHOT)
932 if (cl_particles_bulletimpacts.integer)
934 if (cl_particles_quake.integer)
935 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
938 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
939 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
940 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);
944 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
945 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
947 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
949 if (cl_particles_bulletimpacts.integer)
951 if (cl_particles_quake.integer)
952 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
955 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
956 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
957 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);
961 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
962 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
963 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);
965 else if (effectnameindex == EFFECT_TE_EXPLOSION)
967 CL_ParticleExplosion(center);
968 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);
970 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
972 CL_ParticleExplosion(center);
973 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);
975 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
977 if (cl_particles_quake.integer)
980 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
983 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);
985 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);
989 CL_ParticleExplosion(center);
990 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);
992 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
993 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);
994 else if (effectnameindex == EFFECT_TE_FLAMEJET)
996 count *= cl_particles_quality.value;
998 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);
1000 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1002 float i, j, inc, vel;
1005 inc = 8 / cl_particles_quality.value;
1006 for (i = -128;i < 128;i += inc)
1008 for (j = -128;j < 128;j += inc)
1010 dir[0] = j + lhrandom(0, inc);
1011 dir[1] = i + lhrandom(0, inc);
1013 org[0] = center[0] + dir[0];
1014 org[1] = center[1] + dir[1];
1015 org[2] = center[2] + lhrandom(0, 64);
1016 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1017 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);
1021 else if (effectnameindex == EFFECT_TE_TELEPORT)
1023 float i, j, k, inc, vel;
1026 if (cl_particles_quake.integer)
1027 inc = 4 / cl_particles_quality.value;
1029 inc = 8 / cl_particles_quality.value;
1030 for (i = -16;i < 16;i += inc)
1032 for (j = -16;j < 16;j += inc)
1034 for (k = -24;k < 32;k += inc)
1036 VectorSet(dir, i*8, j*8, k*8);
1037 VectorNormalize(dir);
1038 vel = lhrandom(50, 113);
1039 if (cl_particles_quake.integer)
1040 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);
1042 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);
1046 if (!cl_particles_quake.integer)
1047 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);
1048 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);
1050 else if (effectnameindex == EFFECT_TE_TEI_G3)
1051 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);
1052 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1054 if (cl_particles_smoke.integer)
1056 count *= 0.25f * cl_particles_quality.value;
1058 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1061 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1063 CL_ParticleExplosion(center);
1064 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);
1066 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1069 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1070 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1071 if (cl_particles_smoke.integer)
1072 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1073 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);
1074 if (cl_particles_sparks.integer)
1075 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1076 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);
1077 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);
1079 else if (effectnameindex == EFFECT_EF_FLAME)
1081 count *= 300 * cl_particles_quality.value;
1083 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);
1084 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);
1086 else if (effectnameindex == EFFECT_EF_STARDUST)
1088 count *= 200 * cl_particles_quality.value;
1090 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);
1091 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);
1093 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1097 int smoke, blood, bubbles, r, color;
1099 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1102 Vector4Set(light, 0, 0, 0, 0);
1104 if (effectnameindex == EFFECT_TR_ROCKET)
1105 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1106 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1108 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1109 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1111 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1113 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1114 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1118 matrix4x4_t tempmatrix;
1119 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1120 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);
1121 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1125 if (!spawnparticles)
1128 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1131 VectorSubtract(originmaxs, originmins, dir);
1132 len = VectorNormalizeLength(dir);
1135 dec = -ent->persistent.trail_time;
1136 ent->persistent.trail_time += len;
1137 if (ent->persistent.trail_time < 0.01f)
1140 // if we skip out, leave it reset
1141 ent->persistent.trail_time = 0.0f;
1146 // advance into this frame to reach the first puff location
1147 VectorMA(originmins, dec, dir, pos);
1150 smoke = cl_particles.integer && cl_particles_smoke.integer;
1151 blood = cl_particles.integer && cl_particles_blood.integer;
1152 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1153 qd = 1.0f / cl_particles_quality.value;
1160 if (effectnameindex == EFFECT_TR_BLOOD)
1162 if (cl_particles_quake.integer)
1164 color = particlepalette[67 + (rand()&3)];
1165 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);
1170 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);
1173 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1175 if (cl_particles_quake.integer)
1178 color = particlepalette[67 + (rand()&3)];
1179 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);
1184 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);
1190 if (effectnameindex == EFFECT_TR_ROCKET)
1192 if (cl_particles_quake.integer)
1195 color = particlepalette[ramp3[r]];
1196 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);
1200 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);
1201 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);
1204 else if (effectnameindex == EFFECT_TR_GRENADE)
1206 if (cl_particles_quake.integer)
1209 color = particlepalette[ramp3[r]];
1210 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);
1214 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);
1217 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1219 if (cl_particles_quake.integer)
1222 color = particlepalette[52 + (rand()&7)];
1223 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);
1224 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);
1226 else if (gamemode == GAME_GOODVSBAD2)
1229 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);
1233 color = particlepalette[20 + (rand()&7)];
1234 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1237 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1239 if (cl_particles_quake.integer)
1242 color = particlepalette[230 + (rand()&7)];
1243 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);
1244 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);
1248 color = particlepalette[226 + (rand()&7)];
1249 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);
1252 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1254 if (cl_particles_quake.integer)
1256 color = particlepalette[152 + (rand()&3)];
1257 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);
1259 else if (gamemode == GAME_GOODVSBAD2)
1262 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);
1264 else if (gamemode == GAME_PRYDON)
1267 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);
1270 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);
1272 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1275 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);
1277 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1280 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);
1282 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1283 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);
1287 if (effectnameindex == EFFECT_TR_ROCKET)
1288 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);
1289 else if (effectnameindex == EFFECT_TR_GRENADE)
1290 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);
1292 // advance to next time and position
1295 VectorMA (pos, dec, dir, pos);
1298 ent->persistent.trail_time = len;
1301 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1304 // this is also called on point effects with spawndlight = true and
1305 // spawnparticles = true
1306 // it is called CL_ParticleTrail because most code does not want to supply
1307 // these parameters, only trail handling does
1308 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)
1311 qboolean found = false;
1312 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1314 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1315 return; // no such effect
1317 VectorLerp(originmins, 0.5, originmaxs, center);
1318 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1320 int effectinfoindex;
1323 particleeffectinfo_t *info;
1325 vec3_t centervelocity;
1331 qboolean underwater;
1332 qboolean immediatebloodstain;
1334 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1335 VectorLerp(originmins, 0.5, originmaxs, center);
1336 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1337 supercontents = CL_PointSuperContents(center);
1338 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1339 VectorSubtract(originmaxs, originmins, traildir);
1340 traillen = VectorLength(traildir);
1341 VectorNormalize(traildir);
1342 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1344 if (info->effectnameindex == effectnameindex)
1347 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1349 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1352 // spawn a dlight if requested
1353 if (info->lightradiusstart > 0 && spawndlight)
1355 matrix4x4_t tempmatrix;
1356 if (info->trailspacing > 0)
1357 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1359 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1360 if (info->lighttime > 0 && info->lightradiusfade > 0)
1362 // light flash (explosion, etc)
1363 // called when effect starts
1364 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);
1369 // called by CL_LinkNetworkEntity
1370 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1371 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);
1372 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1376 if (!spawnparticles)
1381 if (info->tex[1] > info->tex[0])
1383 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1384 tex = min(tex, info->tex[1] - 1);
1386 if(info->staintex[0] < 0)
1387 staintex = info->staintex[0];
1390 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1391 staintex = min(staintex, info->staintex[1] - 1);
1393 if (info->particletype == pt_decal)
1394 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]);
1395 else if (info->orientation == PARTICLE_HBEAM)
1396 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);
1399 if (!cl_particles.integer)
1401 switch (info->particletype)
1403 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1404 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1405 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1406 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1407 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1408 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1411 VectorCopy(originmins, trailpos);
1412 if (info->trailspacing > 0)
1414 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1415 trailstep = info->trailspacing / cl_particles_quality.value;
1416 immediatebloodstain = false;
1420 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1422 immediatebloodstain = info->particletype == pt_blood || staintex;
1424 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1425 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1427 if (info->tex[1] > info->tex[0])
1429 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1430 tex = min(tex, info->tex[1] - 1);
1434 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1435 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1436 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1439 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);
1440 if (immediatebloodstain && part)
1442 immediatebloodstain = false;
1443 CL_ImmediateBloodStain(part);
1446 VectorMA(trailpos, trailstep, traildir, trailpos);
1453 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1456 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)
1458 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1466 void CL_EntityParticles (const entity_t *ent)
1469 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1470 static vec3_t avelocities[NUMVERTEXNORMALS];
1471 if (!cl_particles.integer) return;
1472 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1474 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1476 if (!avelocities[0][0])
1477 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1478 avelocities[0][i] = lhrandom(0, 2.55);
1480 for (i = 0;i < NUMVERTEXNORMALS;i++)
1482 yaw = cl.time * avelocities[i][0];
1483 pitch = cl.time * avelocities[i][1];
1484 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1485 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1486 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1487 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);
1492 void CL_ReadPointFile_f (void)
1494 vec3_t org, leakorg;
1496 char *pointfile = NULL, *pointfilepos, *t, tchar;
1497 char name[MAX_OSPATH];
1502 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1503 strlcat (name, ".pts", sizeof (name));
1504 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1507 Con_Printf("Could not open %s\n", name);
1511 Con_Printf("Reading %s...\n", name);
1512 VectorClear(leakorg);
1515 pointfilepos = pointfile;
1516 while (*pointfilepos)
1518 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1523 while (*t && *t != '\n' && *t != '\r')
1527 #if _MSC_VER >= 1400
1528 #define sscanf sscanf_s
1530 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1536 VectorCopy(org, leakorg);
1539 if (cl.num_particles < cl.max_particles - 3)
1542 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);
1545 Mem_Free(pointfile);
1546 VectorCopy(leakorg, org);
1547 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1549 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);
1550 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);
1551 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);
1556 CL_ParseParticleEffect
1558 Parse an effect out of the server message
1561 void CL_ParseParticleEffect (void)
1564 int i, count, msgcount, color;
1566 MSG_ReadVector(org, cls.protocol);
1567 for (i=0 ; i<3 ; i++)
1568 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1569 msgcount = MSG_ReadByte ();
1570 color = MSG_ReadByte ();
1572 if (msgcount == 255)
1577 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1582 CL_ParticleExplosion
1586 void CL_ParticleExplosion (const vec3_t org)
1592 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1593 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1595 if (cl_particles_quake.integer)
1597 for (i = 0;i < 1024;i++)
1603 color = particlepalette[ramp1[r]];
1604 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);
1608 color = particlepalette[ramp2[r]];
1609 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);
1615 i = CL_PointSuperContents(org);
1616 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1618 if (cl_particles.integer && cl_particles_bubbles.integer)
1619 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1620 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);
1624 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1626 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1630 for (k = 0;k < 16;k++)
1633 VectorMA(org, 128, v2, v);
1634 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1635 if (trace.fraction >= 0.1)
1638 VectorSubtract(trace.endpos, org, v2);
1639 VectorScale(v2, 2.0f, v2);
1640 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);
1646 if (cl_particles_explosions_shell.integer)
1647 R_NewExplosion(org);
1652 CL_ParticleExplosion2
1656 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1659 if (!cl_particles.integer) return;
1661 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1663 k = particlepalette[colorStart + (i % colorLength)];
1664 if (cl_particles_quake.integer)
1665 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);
1667 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);
1671 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1673 if (cl_particles_sparks.integer)
1675 sparkcount *= cl_particles_quality.value;
1676 while(sparkcount-- > 0)
1677 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);
1681 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1683 if (cl_particles_smoke.integer)
1685 smokecount *= cl_particles_quality.value;
1686 while(smokecount-- > 0)
1687 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);
1691 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)
1694 if (!cl_particles.integer) return;
1696 count = (int)(count * cl_particles_quality.value);
1699 k = particlepalette[colorbase + (rand()&3)];
1700 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);
1704 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1707 float minz, maxz, lifetime = 30;
1708 if (!cl_particles.integer) return;
1709 if (dir[2] < 0) // falling
1711 minz = maxs[2] + dir[2] * 0.1;
1714 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1719 maxz = maxs[2] + dir[2] * 0.1;
1721 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1724 count = (int)(count * cl_particles_quality.value);
1729 if (!cl_particles_rain.integer) break;
1730 count *= 4; // ick, this should be in the mod or maps?
1734 k = particlepalette[colorbase + (rand()&3)];
1735 if (gamemode == GAME_GOODVSBAD2)
1736 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);
1738 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);
1742 if (!cl_particles_snow.integer) break;
1745 k = particlepalette[colorbase + (rand()&3)];
1746 if (gamemode == GAME_GOODVSBAD2)
1747 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);
1749 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);
1753 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1757 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1758 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1759 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1760 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1762 #define PARTICLETEXTURESIZE 64
1763 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1765 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1769 dz = 1 - (dx*dx+dy*dy);
1770 if (dz > 0) // it does hit the sphere
1774 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1775 VectorNormalize(normal);
1776 dot = DotProduct(normal, light);
1777 if (dot > 0.5) // interior reflection
1778 f += ((dot * 2) - 1);
1779 else if (dot < -0.5) // exterior reflection
1780 f += ((dot * -2) - 1);
1782 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1783 VectorNormalize(normal);
1784 dot = DotProduct(normal, light);
1785 if (dot > 0.5) // interior reflection
1786 f += ((dot * 2) - 1);
1787 else if (dot < -0.5) // exterior reflection
1788 f += ((dot * -2) - 1);
1790 f += 16; // just to give it a haze so you can see the outline
1791 f = bound(0, f, 255);
1792 return (unsigned char) f;
1798 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1799 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1801 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1802 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1803 *width = particlefontcellwidth;
1804 *height = particlefontcellheight;
1807 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1809 int basex, basey, w, h, y;
1810 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1811 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1812 Sys_Error("invalid particle texture size for autogenerating");
1813 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1814 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1817 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1820 float cx, cy, dx, dy, f, iradius;
1822 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1823 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1824 iradius = 1.0f / radius;
1825 alpha *= (1.0f / 255.0f);
1826 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1828 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1832 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1837 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1838 d[0] += (int)(f * (blue - d[0]));
1839 d[1] += (int)(f * (green - d[1]));
1840 d[2] += (int)(f * (red - d[2]));
1846 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1849 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1851 data[0] = bound(minb, data[0], maxb);
1852 data[1] = bound(ming, data[1], maxg);
1853 data[2] = bound(minr, data[2], maxr);
1857 void particletextureinvert(unsigned char *data)
1860 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1862 data[0] = 255 - data[0];
1863 data[1] = 255 - data[1];
1864 data[2] = 255 - data[2];
1868 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1869 static void R_InitBloodTextures (unsigned char *particletexturedata)
1872 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1873 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1876 for (i = 0;i < 8;i++)
1878 memset(data, 255, datasize);
1879 for (k = 0;k < 24;k++)
1880 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1881 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1882 particletextureinvert(data);
1883 setuptex(tex_bloodparticle[i], data, particletexturedata);
1887 for (i = 0;i < 8;i++)
1889 memset(data, 255, datasize);
1891 for (j = 1;j < 10;j++)
1892 for (k = min(j, m - 1);k < m;k++)
1893 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1894 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1895 particletextureinvert(data);
1896 setuptex(tex_blooddecal[i], data, particletexturedata);
1902 //uncomment this to make engine save out particle font to a tga file when run
1903 //#define DUMPPARTICLEFONT
1905 static void R_InitParticleTexture (void)
1907 int x, y, d, i, k, m;
1908 int basex, basey, w, h;
1912 fs_offset_t filesize;
1914 // a note: decals need to modulate (multiply) the background color to
1915 // properly darken it (stain), and they need to be able to alpha fade,
1916 // this is a very difficult challenge because it means fading to white
1917 // (no change to background) rather than black (darkening everything
1918 // behind the whole decal polygon), and to accomplish this the texture is
1919 // inverted (dark red blood on white background becomes brilliant cyan
1920 // and white on black background) so we can alpha fade it to black, then
1921 // we invert it again during the blendfunc to make it work...
1923 #ifndef DUMPPARTICLEFONT
1924 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1927 particlefonttexture = decalskinframe->base;
1928 // TODO maybe allow custom grid size?
1929 particlefontwidth = image_width;
1930 particlefontheight = image_height;
1931 particlefontcellwidth = image_width / 8;
1932 particlefontcellheight = image_height / 8;
1933 particlefontcols = 8;
1934 particlefontrows = 8;
1939 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1940 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1941 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1942 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1943 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1945 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1946 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1947 particlefontcols = 8;
1948 particlefontrows = 8;
1950 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1953 for (i = 0;i < 8;i++)
1955 memset(data, 255, datasize);
1958 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1959 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1961 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1963 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1964 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1966 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1967 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1969 d = (int)(d * (1-(dx*dx+dy*dy)));
1970 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1971 d = bound(0, d, 255);
1972 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1979 setuptex(tex_smoke[i], data, particletexturedata);
1983 memset(data, 255, datasize);
1984 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1986 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1987 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1989 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1990 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1991 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
1994 setuptex(tex_rainsplash, data, particletexturedata);
1997 memset(data, 255, datasize);
1998 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2000 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2001 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2003 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2004 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2005 d = bound(0, d, 255);
2006 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2009 setuptex(tex_particle, data, particletexturedata);
2012 memset(data, 255, datasize);
2013 light[0] = 1;light[1] = 1;light[2] = 1;
2014 VectorNormalize(light);
2015 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2017 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2018 // stretch upper half of bubble by +50% and shrink lower half by -50%
2019 // (this gives an elongated teardrop shape)
2021 dy = (dy - 0.5f) * 2.0f;
2023 dy = (dy - 0.5f) / 1.5f;
2024 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2026 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2027 // shrink bubble width to half
2029 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2032 setuptex(tex_raindrop, data, particletexturedata);
2035 memset(data, 255, datasize);
2036 light[0] = 1;light[1] = 1;light[2] = 1;
2037 VectorNormalize(light);
2038 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2040 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2041 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2043 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2044 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2047 setuptex(tex_bubble, data, particletexturedata);
2049 // Blood particles and blood decals
2050 R_InitBloodTextures (particletexturedata);
2053 for (i = 0;i < 8;i++)
2055 memset(data, 255, datasize);
2056 for (k = 0;k < 12;k++)
2057 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2058 for (k = 0;k < 3;k++)
2059 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2060 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2061 particletextureinvert(data);
2062 setuptex(tex_bulletdecal[i], data, particletexturedata);
2065 #ifdef DUMPPARTICLEFONT
2066 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2069 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2070 particlefonttexture = decalskinframe->base;
2072 Mem_Free(particletexturedata);
2077 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2079 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2080 particletexture[i].texture = particlefonttexture;
2081 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2082 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2083 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2084 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2087 #ifndef DUMPPARTICLEFONT
2088 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true);
2089 if (!particletexture[tex_beam].texture)
2092 unsigned char noise3[64][64], data2[64][16][4];
2094 fractalnoise(&noise3[0][0], 64, 4);
2096 for (y = 0;y < 64;y++)
2098 dy = (y - 0.5f*64) / (64*0.5f-1);
2099 for (x = 0;x < 16;x++)
2101 dx = (x - 0.5f*16) / (16*0.5f-2);
2102 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2103 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2104 data2[y][x][3] = 255;
2108 #ifdef DUMPPARTICLEFONT
2109 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2111 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2113 particletexture[tex_beam].s1 = 0;
2114 particletexture[tex_beam].t1 = 0;
2115 particletexture[tex_beam].s2 = 1;
2116 particletexture[tex_beam].t2 = 1;
2118 // now load an texcoord/texture override file
2119 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2126 if(!COM_ParseToken_Simple(&bufptr, true, false))
2128 if(!strcmp(com_token, "\n"))
2129 continue; // empty line
2130 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2131 particletexture[i].texture = particlefonttexture;
2133 if (!COM_ParseToken_Simple(&bufptr, true, false))
2135 if (!strcmp(com_token, "\n"))
2137 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2140 particletexture[i].s1 = atof(com_token);
2142 if (!COM_ParseToken_Simple(&bufptr, true, false))
2144 if (!strcmp(com_token, "\n"))
2146 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2149 particletexture[i].t1 = atof(com_token);
2151 if (!COM_ParseToken_Simple(&bufptr, true, false))
2153 if (!strcmp(com_token, "\n"))
2155 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2158 particletexture[i].s2 = atof(com_token);
2160 if (!COM_ParseToken_Simple(&bufptr, true, false))
2162 if (!strcmp(com_token, "\n"))
2164 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2167 particletexture[i].t2 = atof(com_token);
2173 static void r_part_start(void)
2176 // generate particlepalette for convenience from the main one
2177 for (i = 0;i < 256;i++)
2178 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2179 particletexturepool = R_AllocTexturePool();
2180 R_InitParticleTexture ();
2181 CL_Particles_LoadEffectInfo();
2184 static void r_part_shutdown(void)
2186 R_FreeTexturePool(&particletexturepool);
2189 static void r_part_newmap(void)
2192 R_SkinFrame_MarkUsed(decalskinframe);
2193 CL_Particles_LoadEffectInfo();
2196 #define BATCHSIZE 256
2197 unsigned short particle_elements[BATCHSIZE*6];
2198 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2200 void R_Particles_Init (void)
2203 for (i = 0;i < BATCHSIZE;i++)
2205 particle_elements[i*6+0] = i*4+0;
2206 particle_elements[i*6+1] = i*4+1;
2207 particle_elements[i*6+2] = i*4+2;
2208 particle_elements[i*6+3] = i*4+0;
2209 particle_elements[i*6+4] = i*4+2;
2210 particle_elements[i*6+5] = i*4+3;
2213 Cvar_RegisterVariable(&r_drawparticles);
2214 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2215 Cvar_RegisterVariable(&r_drawdecals);
2216 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2217 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2220 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2222 int surfacelistindex;
2224 float *v3f, *t2f, *c4f;
2225 particletexture_t *tex;
2226 float right[3], up[3], size, ca;
2227 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2228 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2230 RSurf_ActiveWorldEntity();
2232 r_refdef.stats.drawndecals += numsurfaces;
2233 R_Mesh_ResetTextureState();
2234 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2235 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2236 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2237 GL_DepthMask(false);
2238 GL_DepthRange(0, 1);
2239 GL_PolygonOffset(0, 0);
2241 GL_CullFace(GL_NONE);
2243 // generate all the vertices at once
2244 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2246 d = cl.decals + surfacelist[surfacelistindex];
2249 c4f = particle_color4f + 16*surfacelistindex;
2250 ca = d->alpha * alphascale;
2251 if (r_refdef.fogenabled)
2252 ca *= RSurf_FogVertex(d->org);
2253 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2254 Vector4Copy(c4f, c4f + 4);
2255 Vector4Copy(c4f, c4f + 8);
2256 Vector4Copy(c4f, c4f + 12);
2258 // calculate vertex positions
2259 size = d->size * cl_particles_size.value;
2260 VectorVectors(d->normal, right, up);
2261 VectorScale(right, size, right);
2262 VectorScale(up, size, up);
2263 v3f = particle_vertex3f + 12*surfacelistindex;
2264 v3f[ 0] = d->org[0] - right[0] - up[0];
2265 v3f[ 1] = d->org[1] - right[1] - up[1];
2266 v3f[ 2] = d->org[2] - right[2] - up[2];
2267 v3f[ 3] = d->org[0] - right[0] + up[0];
2268 v3f[ 4] = d->org[1] - right[1] + up[1];
2269 v3f[ 5] = d->org[2] - right[2] + up[2];
2270 v3f[ 6] = d->org[0] + right[0] + up[0];
2271 v3f[ 7] = d->org[1] + right[1] + up[1];
2272 v3f[ 8] = d->org[2] + right[2] + up[2];
2273 v3f[ 9] = d->org[0] + right[0] - up[0];
2274 v3f[10] = d->org[1] + right[1] - up[1];
2275 v3f[11] = d->org[2] + right[2] - up[2];
2277 // calculate texcoords
2278 tex = &particletexture[d->texnum];
2279 t2f = particle_texcoord2f + 8*surfacelistindex;
2280 t2f[0] = tex->s1;t2f[1] = tex->t2;
2281 t2f[2] = tex->s1;t2f[3] = tex->t1;
2282 t2f[4] = tex->s2;t2f[5] = tex->t1;
2283 t2f[6] = tex->s2;t2f[7] = tex->t2;
2286 // now render the decals all at once
2287 // (this assumes they all use one particle font texture!)
2288 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2289 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2290 GL_LockArrays(0, numsurfaces*4);
2291 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2292 GL_LockArrays(0, 0);
2295 void R_DrawDecals (void)
2298 int drawdecals = r_drawdecals.integer;
2303 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2305 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2306 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2308 // LordHavoc: early out conditions
2312 decalfade = frametime * 256 / cl_decals_fadetime.value;
2313 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2314 drawdist2 = drawdist2*drawdist2;
2316 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2318 if (!decal->typeindex)
2321 if (killsequence - decal->decalsequence > 0)
2324 if (cl.time > decal->time2 + cl_decals_time.value)
2326 decal->alpha -= decalfade;
2327 if (decal->alpha <= 0)
2333 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2335 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2336 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2342 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2348 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))
2349 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2352 decal->typeindex = 0;
2353 if (cl.free_decal > i)
2357 // reduce cl.num_decals if possible
2358 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2361 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2363 decal_t *olddecals = cl.decals;
2364 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2365 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2366 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2367 Mem_Free(olddecals);
2370 r_refdef.stats.totaldecals = cl.num_decals;
2373 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2375 int surfacelistindex;
2376 int batchstart, batchcount;
2377 const particle_t *p;
2379 rtexture_t *texture;
2380 float *v3f, *t2f, *c4f;
2381 particletexture_t *tex;
2382 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2383 float ambient[3], diffuse[3], diffusenormal[3];
2384 vec4_t colormultiplier;
2386 RSurf_ActiveWorldEntity();
2388 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));
2390 r_refdef.stats.particles += numsurfaces;
2391 R_Mesh_ResetTextureState();
2392 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2393 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2394 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2395 GL_DepthMask(false);
2396 GL_DepthRange(0, 1);
2397 GL_PolygonOffset(0, 0);
2399 GL_CullFace(GL_NONE);
2401 // first generate all the vertices at once
2402 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2404 p = cl.particles + surfacelist[surfacelistindex];
2406 blendmode = p->blendmode;
2410 case PBLEND_INVALID:
2412 c4f[0] = p->color[0] * (1.0f / 256.0f);
2413 c4f[1] = p->color[1] * (1.0f / 256.0f);
2414 c4f[2] = p->color[2] * (1.0f / 256.0f);
2415 c4f[3] = p->alpha * colormultiplier[3];
2416 // additive and modulate can just fade out in fog (this is correct)
2417 if (r_refdef.fogenabled)
2418 c4f[3] *= RSurf_FogVertex(p->org);
2419 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2426 c4f[0] = p->color[0] * colormultiplier[0];
2427 c4f[1] = p->color[1] * colormultiplier[1];
2428 c4f[2] = p->color[2] * colormultiplier[2];
2429 c4f[3] = p->alpha * colormultiplier[3];
2430 // additive and modulate can just fade out in fog (this is correct)
2431 if (r_refdef.fogenabled)
2432 c4f[3] *= RSurf_FogVertex(p->org);
2433 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2440 c4f[0] = p->color[0] * colormultiplier[0];
2441 c4f[1] = p->color[1] * colormultiplier[1];
2442 c4f[2] = p->color[2] * colormultiplier[2];
2443 c4f[3] = p->alpha * colormultiplier[3];
2444 // note: lighting is not cheap!
2445 if (particletype[p->typeindex].lighting)
2447 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2448 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2449 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2450 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2452 // mix in the fog color
2453 if (r_refdef.fogenabled)
2455 fog = RSurf_FogVertex(p->org);
2457 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2458 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2459 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2463 // copy the color into the other three vertices
2464 Vector4Copy(c4f, c4f + 4);
2465 Vector4Copy(c4f, c4f + 8);
2466 Vector4Copy(c4f, c4f + 12);
2468 size = p->size * cl_particles_size.value;
2469 tex = &particletexture[p->texnum];
2470 switch(p->orientation)
2472 case PARTICLE_INVALID:
2473 case PARTICLE_BILLBOARD:
2474 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2475 VectorScale(r_refdef.view.up, size, up);
2476 v3f[ 0] = p->org[0] - right[0] - up[0];
2477 v3f[ 1] = p->org[1] - right[1] - up[1];
2478 v3f[ 2] = p->org[2] - right[2] - up[2];
2479 v3f[ 3] = p->org[0] - right[0] + up[0];
2480 v3f[ 4] = p->org[1] - right[1] + up[1];
2481 v3f[ 5] = p->org[2] - right[2] + up[2];
2482 v3f[ 6] = p->org[0] + right[0] + up[0];
2483 v3f[ 7] = p->org[1] + right[1] + up[1];
2484 v3f[ 8] = p->org[2] + right[2] + up[2];
2485 v3f[ 9] = p->org[0] + right[0] - up[0];
2486 v3f[10] = p->org[1] + right[1] - up[1];
2487 v3f[11] = p->org[2] + right[2] - up[2];
2488 t2f[0] = tex->s1;t2f[1] = tex->t2;
2489 t2f[2] = tex->s1;t2f[3] = tex->t1;
2490 t2f[4] = tex->s2;t2f[5] = tex->t1;
2491 t2f[6] = tex->s2;t2f[7] = tex->t2;
2493 case PARTICLE_ORIENTED_DOUBLESIDED:
2494 VectorVectors(p->vel, right, up);
2495 VectorScale(right, size * p->stretch, right);
2496 VectorScale(up, size, up);
2497 v3f[ 0] = p->org[0] - right[0] - up[0];
2498 v3f[ 1] = p->org[1] - right[1] - up[1];
2499 v3f[ 2] = p->org[2] - right[2] - up[2];
2500 v3f[ 3] = p->org[0] - right[0] + up[0];
2501 v3f[ 4] = p->org[1] - right[1] + up[1];
2502 v3f[ 5] = p->org[2] - right[2] + up[2];
2503 v3f[ 6] = p->org[0] + right[0] + up[0];
2504 v3f[ 7] = p->org[1] + right[1] + up[1];
2505 v3f[ 8] = p->org[2] + right[2] + up[2];
2506 v3f[ 9] = p->org[0] + right[0] - up[0];
2507 v3f[10] = p->org[1] + right[1] - up[1];
2508 v3f[11] = p->org[2] + right[2] - up[2];
2509 t2f[0] = tex->s1;t2f[1] = tex->t2;
2510 t2f[2] = tex->s1;t2f[3] = tex->t1;
2511 t2f[4] = tex->s2;t2f[5] = tex->t1;
2512 t2f[6] = tex->s2;t2f[7] = tex->t2;
2514 case PARTICLE_SPARK:
2515 len = VectorLength(p->vel);
2516 VectorNormalize2(p->vel, up);
2517 lenfactor = p->stretch * 0.04 * len;
2518 if(lenfactor < size * 0.5)
2519 lenfactor = size * 0.5;
2520 VectorMA(p->org, -lenfactor, up, v);
2521 VectorMA(p->org, lenfactor, up, up2);
2522 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2523 t2f[0] = tex->s1;t2f[1] = tex->t2;
2524 t2f[2] = tex->s1;t2f[3] = tex->t1;
2525 t2f[4] = tex->s2;t2f[5] = tex->t1;
2526 t2f[6] = tex->s2;t2f[7] = tex->t2;
2528 case PARTICLE_VBEAM:
2529 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2530 VectorSubtract(p->vel, p->org, up);
2531 VectorNormalize(up);
2532 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2533 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2534 t2f[0] = tex->s2;t2f[1] = v[0];
2535 t2f[2] = tex->s1;t2f[3] = v[0];
2536 t2f[4] = tex->s1;t2f[5] = v[1];
2537 t2f[6] = tex->s2;t2f[7] = v[1];
2539 case PARTICLE_HBEAM:
2540 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2541 VectorSubtract(p->vel, p->org, up);
2542 VectorNormalize(up);
2543 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2544 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2545 t2f[0] = v[0];t2f[1] = tex->t1;
2546 t2f[2] = v[0];t2f[3] = tex->t2;
2547 t2f[4] = v[1];t2f[5] = tex->t2;
2548 t2f[6] = v[1];t2f[7] = tex->t1;
2553 // now render batches of particles based on blendmode and texture
2554 blendmode = PBLEND_INVALID;
2556 GL_LockArrays(0, numsurfaces*4);
2559 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2561 p = cl.particles + surfacelist[surfacelistindex];
2563 if (blendmode != p->blendmode)
2565 blendmode = p->blendmode;
2569 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2571 case PBLEND_INVALID:
2573 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2576 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2580 if (texture != particletexture[p->texnum].texture)
2582 texture = particletexture[p->texnum].texture;
2583 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2586 // iterate until we find a change in settings
2587 batchstart = surfacelistindex++;
2588 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2590 p = cl.particles + surfacelist[surfacelistindex];
2591 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2595 batchcount = surfacelistindex - batchstart;
2596 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2598 GL_LockArrays(0, 0);
2601 void R_DrawParticles (void)
2604 int drawparticles = r_drawparticles.integer;
2605 float minparticledist;
2607 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2613 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2614 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2616 // LordHavoc: early out conditions
2617 if (!cl.num_particles)
2620 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2621 gravity = frametime * cl.movevars_gravity;
2622 dvel = 1+4*frametime;
2623 decalfade = frametime * 255 / cl_decals_fadetime.value;
2624 update = frametime > 0;
2625 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2626 drawdist2 = drawdist2*drawdist2;
2628 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2632 if (cl.free_particle > i)
2633 cl.free_particle = i;
2639 if (p->delayedspawn > cl.time)
2641 p->delayedspawn = 0;
2645 p->size += p->sizeincrease * frametime;
2646 p->alpha -= p->alphafade * frametime;
2648 if (p->alpha <= 0 || p->die <= cl.time)
2651 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2653 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2655 if (p->typeindex == pt_blood)
2656 p->size += frametime * 8;
2658 p->vel[2] -= p->gravity * gravity;
2659 f = 1.0f - min(p->liquidfriction * frametime, 1);
2660 VectorScale(p->vel, f, p->vel);
2664 p->vel[2] -= p->gravity * gravity;
2667 f = 1.0f - min(p->airfriction * frametime, 1);
2668 VectorScale(p->vel, f, p->vel);
2672 VectorCopy(p->org, oldorg);
2673 VectorMA(p->org, frametime, p->vel, p->org);
2674 if (p->bounce && cl.time >= p->delayedcollisions)
2676 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);
2677 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2678 // or if the trace hit something flagged as NOIMPACT
2679 // then remove the particle
2680 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2682 VectorCopy(trace.endpos, p->org);
2683 // react if the particle hit something
2684 if (trace.fraction < 1)
2686 VectorCopy(trace.endpos, p->org);
2688 if (p->staintexnum >= 0)
2690 // blood - splash on solid
2691 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2694 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2695 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2696 if (cl_decals.integer)
2698 // create a decal for the blood splat
2699 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!
2704 if (p->typeindex == pt_blood)
2706 // blood - splash on solid
2707 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2709 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2711 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)));
2712 if (cl_decals.integer)
2714 // create a decal for the blood splat
2715 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 * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768);
2720 else if (p->bounce < 0)
2722 // bounce -1 means remove on impact
2727 // anything else - bounce off solid
2728 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2729 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2730 if (DotProduct(p->vel, p->vel) < 0.03)
2731 VectorClear(p->vel);
2737 if (p->typeindex != pt_static)
2739 switch (p->typeindex)
2741 case pt_entityparticle:
2742 // particle that removes itself after one rendered frame
2749 a = CL_PointSuperContents(p->org);
2750 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2754 a = CL_PointSuperContents(p->org);
2755 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2759 a = CL_PointSuperContents(p->org);
2760 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2764 if (cl.time > p->time2)
2767 p->time2 = cl.time + (rand() & 3) * 0.1;
2768 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2769 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2771 a = CL_PointSuperContents(p->org);
2772 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2780 else if (p->delayedspawn)
2784 // don't render particles too close to the view (they chew fillrate)
2785 // also don't render particles behind the view (useless)
2786 // further checks to cull to the frustum would be too slow here
2787 switch(p->typeindex)
2790 // beams have no culling
2791 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2794 if(cl_particles_visculling.integer)
2795 if (!r_refdef.viewcache.world_novis)
2796 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2798 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2800 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2803 // anything else just has to be in front of the viewer and visible at this distance
2804 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2805 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2812 if (cl.free_particle > i)
2813 cl.free_particle = i;
2816 // reduce cl.num_particles if possible
2817 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2820 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2822 particle_t *oldparticles = cl.particles;
2823 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2824 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2825 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2826 Mem_Free(oldparticles);