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!
120 particleeffectinfo_t;
122 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
125 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
127 static int particlepalette[256];
129 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
130 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
131 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
132 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
133 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
134 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
135 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
136 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
137 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
138 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
139 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
140 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
141 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
142 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
143 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
144 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
145 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
146 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
147 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
148 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
149 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
150 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
151 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
152 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
153 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
154 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
155 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
156 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
157 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
158 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
159 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
160 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
163 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
164 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
165 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
167 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
169 // particletexture_t is a rectangle in the particlefonttexture
170 typedef struct particletexture_s
173 float s1, t1, s2, t2;
177 static rtexturepool_t *particletexturepool;
178 static rtexture_t *particlefonttexture;
179 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
180 skinframe_t *decalskinframe;
182 // texture numbers in particle font
183 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
184 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
185 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
186 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
187 static const int tex_rainsplash = 32;
188 static const int tex_particle = 63;
189 static const int tex_bubble = 62;
190 static const int tex_raindrop = 61;
191 static const int tex_beam = 60;
193 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
194 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
195 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
196 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
197 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
198 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
199 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
200 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
201 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
202 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
203 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
204 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
205 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
206 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
207 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
208 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
209 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
210 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
211 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
212 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
213 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
214 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
215 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
216 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
217 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
218 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
219 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
220 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)"};
221 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
222 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
223 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
226 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
232 particleeffectinfo_t *info = NULL;
233 const char *text = textstart;
235 effectinfoindex = -1;
236 for (linenumber = 1;;linenumber++)
239 for (arrayindex = 0;arrayindex < 16;arrayindex++)
240 argv[arrayindex][0] = 0;
243 if (!COM_ParseToken_Simple(&text, true, false))
245 if (!strcmp(com_token, "\n"))
249 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
255 #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;}
256 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
257 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
258 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
259 #define readfloat(var) checkparms(2);var = atof(argv[1])
260 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
261 if (!strcmp(argv[0], "effect"))
266 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
268 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
271 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
273 if (particleeffectname[effectnameindex][0])
275 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
280 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
284 // if we run out of names, abort
285 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
287 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
290 info = particleeffectinfo + effectinfoindex;
291 info->effectnameindex = effectnameindex;
292 info->particletype = pt_alphastatic;
293 info->blendmode = particletype[info->particletype].blendmode;
294 info->orientation = particletype[info->particletype].orientation;
295 info->tex[0] = tex_particle;
296 info->tex[1] = tex_particle;
297 info->color[0] = 0xFFFFFF;
298 info->color[1] = 0xFFFFFF;
302 info->alpha[1] = 256;
303 info->alpha[2] = 256;
304 info->time[0] = 9999;
305 info->time[1] = 9999;
306 VectorSet(info->lightcolor, 1, 1, 1);
307 info->lightshadow = true;
308 info->lighttime = 9999;
309 info->stretchfactor = 1;
310 info->staincolor[0] = (unsigned int)-1;
311 info->staincolor[1] = (unsigned int)-1;
312 info->staintex[0] = -1;
313 info->staintex[1] = -1;
314 info->stainalpha[0] = 1;
315 info->stainalpha[1] = 1;
316 info->stainsize[0] = 2;
317 info->stainsize[1] = 2;
319 else if (info == NULL)
321 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
324 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
325 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
326 else if (!strcmp(argv[0], "type"))
329 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
330 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
331 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
332 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
333 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
334 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
335 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
336 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
337 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
338 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
339 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
340 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
341 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
342 info->blendmode = particletype[info->particletype].blendmode;
343 info->orientation = particletype[info->particletype].orientation;
345 else if (!strcmp(argv[0], "blend"))
348 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
349 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
350 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
351 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
353 else if (!strcmp(argv[0], "orientation"))
356 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
357 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
358 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
359 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
360 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
362 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
363 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
364 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
365 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
366 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
367 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
368 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
369 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
370 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
371 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
372 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
373 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
374 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
375 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
376 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
377 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
378 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
379 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
380 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
381 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
382 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
383 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
384 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
385 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
386 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
387 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
388 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
389 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
390 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
391 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; }
393 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
402 int CL_ParticleEffectIndexForName(const char *name)
405 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
406 if (!strcmp(particleeffectname[i], name))
411 const char *CL_ParticleEffectNameForIndex(int i)
413 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
415 return particleeffectname[i];
418 // MUST match effectnameindex_t in client.h
419 static const char *standardeffectnames[EFFECT_TOTAL] =
443 "TE_TEI_BIGEXPLOSION",
459 void CL_Particles_LoadEffectInfo(void)
463 unsigned char *filedata;
464 fs_offset_t filesize;
465 char filename[MAX_QPATH];
466 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
467 memset(particleeffectname, 0, sizeof(particleeffectname));
468 for (i = 0;i < EFFECT_TOTAL;i++)
469 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
470 for (filepass = 0;;filepass++)
473 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
474 else if (filepass == 1)
475 dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
478 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
481 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
491 void CL_ReadPointFile_f (void);
492 void CL_Particles_Init (void)
494 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)");
495 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
497 Cvar_RegisterVariable (&cl_particles);
498 Cvar_RegisterVariable (&cl_particles_quality);
499 Cvar_RegisterVariable (&cl_particles_alpha);
500 Cvar_RegisterVariable (&cl_particles_size);
501 Cvar_RegisterVariable (&cl_particles_quake);
502 Cvar_RegisterVariable (&cl_particles_blood);
503 Cvar_RegisterVariable (&cl_particles_blood_alpha);
504 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
505 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
506 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
507 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
508 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
509 Cvar_RegisterVariable (&cl_particles_explosions_shell);
510 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
511 Cvar_RegisterVariable (&cl_particles_rain);
512 Cvar_RegisterVariable (&cl_particles_snow);
513 Cvar_RegisterVariable (&cl_particles_smoke);
514 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
515 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
516 Cvar_RegisterVariable (&cl_particles_sparks);
517 Cvar_RegisterVariable (&cl_particles_bubbles);
518 Cvar_RegisterVariable (&cl_particles_visculling);
519 Cvar_RegisterVariable (&cl_decals);
520 Cvar_RegisterVariable (&cl_decals_visculling);
521 Cvar_RegisterVariable (&cl_decals_time);
522 Cvar_RegisterVariable (&cl_decals_fadetime);
523 Cvar_RegisterVariable (&cl_decals_newsystem);
524 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
525 Cvar_RegisterVariable (&cl_decals_models);
526 Cvar_RegisterVariable (&cl_decals_bias);
527 Cvar_RegisterVariable (&cl_decals_max);
530 void CL_Particles_Shutdown (void)
534 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
535 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
537 // list of all 26 parameters:
538 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
539 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
540 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
541 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
542 // palpha - opacity of particle as 0-255 (can be more than 255)
543 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
544 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
545 // pgravity - how much effect gravity has on the particle (0-1)
546 // 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
547 // px,py,pz - starting origin of particle
548 // pvx,pvy,pvz - starting velocity of particle
549 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
550 // blendmode - one of the PBLEND_ values
551 // orientation - one of the PARTICLE_ values
552 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
553 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
554 // stainalpha: opacity of the stain as factor for alpha
555 // stainsize: size of the stain as factor for palpha
556 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, float stainalpha, float stainsize)
561 if (!cl_particles.integer)
563 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
564 if (cl.free_particle >= cl.max_particles)
567 lifetime = palpha / min(1, palphafade);
568 part = &cl.particles[cl.free_particle++];
569 if (cl.num_particles < cl.free_particle)
570 cl.num_particles = cl.free_particle;
571 memset(part, 0, sizeof(*part));
572 part->typeindex = ptypeindex;
573 part->blendmode = blendmode;
574 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
576 particletexture_t *tex = &particletexture[ptex];
577 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
578 part->orientation = PARTICLE_VBEAM;
580 part->orientation = PARTICLE_HBEAM;
583 part->orientation = orientation;
584 l2 = (int)lhrandom(0.5, 256.5);
586 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
587 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
588 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
589 part->staintexnum = staintex;
590 if(staincolor1 >= 0 && staincolor2 >= 0)
592 l2 = (int)lhrandom(0.5, 256.5);
594 if(blendmode == PBLEND_INVMOD)
596 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
597 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
598 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
602 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
603 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
604 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
606 if(r > 0xFF) r = 0xFF;
607 if(g > 0xFF) g = 0xFF;
608 if(b > 0xFF) b = 0xFF;
612 r = part->color[0]; // -1 is shorthand for stain = particle color
616 part->staincolor = r * 65536 + g * 256 + b;
617 part->stainalpha = palpha * stainalpha / 256;
618 part->stainsize = psize * stainsize;
621 part->sizeincrease = psizeincrease;
622 part->alpha = palpha;
623 part->alphafade = palphafade;
624 part->gravity = pgravity;
625 part->bounce = pbounce;
626 part->stretch = stretch;
628 part->org[0] = px + originjitter * v[0];
629 part->org[1] = py + originjitter * v[1];
630 part->org[2] = pz + originjitter * v[2];
631 part->vel[0] = pvx + velocityjitter * v[0];
632 part->vel[1] = pvy + velocityjitter * v[1];
633 part->vel[2] = pvz + velocityjitter * v[2];
635 part->airfriction = pairfriction;
636 part->liquidfriction = pliquidfriction;
637 part->die = cl.time + lifetime;
638 part->delayedcollisions = 0;
639 part->qualityreduction = pqualityreduction;
640 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
641 if (part->typeindex == pt_rain)
645 float lifetime = part->die - cl.time;
648 // turn raindrop into simple spark and create delayedspawn splash effect
649 part->typeindex = pt_spark;
651 VectorMA(part->org, lifetime, part->vel, endvec);
652 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
653 part->die = cl.time + lifetime * trace.fraction;
654 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, 1, 1);
657 part2->delayedspawn = part->die;
658 part2->die += part->die - cl.time;
659 for (i = rand() & 7;i < 10;i++)
661 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, 1, 1);
664 part2->delayedspawn = part->die;
665 part2->die += part->die - cl.time;
670 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
672 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
675 VectorMA(part->org, lifetime, part->vel, endvec);
676 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
677 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
683 static void CL_ImmediateBloodStain(particle_t *part)
688 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
689 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
691 VectorCopy(part->vel, v);
693 staintex = part->staintexnum;
694 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->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
697 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
698 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
700 VectorCopy(part->vel, v);
702 staintex = tex_blooddecal[rand()&7];
703 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);
707 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
711 entity_render_t *ent = &cl.entities[hitent].render;
712 unsigned char color[3];
713 if (!cl_decals.integer)
715 if (!ent->allowdecals)
718 l2 = (int)lhrandom(0.5, 256.5);
720 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
721 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
722 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
724 if (cl_decals_newsystem.integer)
726 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);
730 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
731 if (cl.free_decal >= cl.max_decals)
733 decal = &cl.decals[cl.free_decal++];
734 if (cl.num_decals < cl.free_decal)
735 cl.num_decals = cl.free_decal;
736 memset(decal, 0, sizeof(*decal));
737 decal->decalsequence = cl.decalsequence++;
738 decal->typeindex = pt_decal;
739 decal->texnum = texnum;
740 VectorMA(org, cl_decals_bias.value, normal, decal->org);
741 VectorCopy(normal, decal->normal);
743 decal->alpha = alpha;
744 decal->time2 = cl.time;
745 decal->color[0] = color[0];
746 decal->color[1] = color[1];
747 decal->color[2] = color[2];
748 decal->owner = hitent;
749 decal->clusterindex = -1000; // no vis culling unless we're sure
752 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
753 decal->ownermodel = cl.entities[decal->owner].render.model;
754 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
755 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
759 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
761 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
763 decal->clusterindex = leaf->clusterindex;
768 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
771 float bestfrac, bestorg[3], bestnormal[3];
773 int besthitent = 0, hitent;
776 for (i = 0;i < 32;i++)
779 VectorMA(org, maxdist, org2, org2);
780 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
781 // take the closest trace result that doesn't end up hitting a NOMARKS
782 // surface (sky for example)
783 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
785 bestfrac = trace.fraction;
787 VectorCopy(trace.endpos, bestorg);
788 VectorCopy(trace.plane.normal, bestnormal);
792 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
795 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
796 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
797 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)
800 matrix4x4_t tempmatrix;
802 VectorLerp(originmins, 0.5, originmaxs, center);
803 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
804 if (effectnameindex == EFFECT_SVC_PARTICLE)
806 if (cl_particles.integer)
808 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
810 CL_ParticleExplosion(center);
811 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
812 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
815 count *= cl_particles_quality.value;
816 for (;count > 0;count--)
818 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
819 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, 1, 1);
824 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
825 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
826 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
827 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
828 else if (effectnameindex == EFFECT_TE_SPIKE)
830 if (cl_particles_bulletimpacts.integer)
832 if (cl_particles_quake.integer)
834 if (cl_particles_smoke.integer)
835 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
839 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
840 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
841 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, 1, 1);
845 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
846 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
848 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
850 if (cl_particles_bulletimpacts.integer)
852 if (cl_particles_quake.integer)
854 if (cl_particles_smoke.integer)
855 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
859 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
860 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
861 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, 1, 1);
865 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
866 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
867 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);
869 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
871 if (cl_particles_bulletimpacts.integer)
873 if (cl_particles_quake.integer)
875 if (cl_particles_smoke.integer)
876 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
880 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
881 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
882 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, 1, 1);
886 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
887 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
889 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
891 if (cl_particles_bulletimpacts.integer)
893 if (cl_particles_quake.integer)
895 if (cl_particles_smoke.integer)
896 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
900 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
901 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
902 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, 1, 1);
906 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
907 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
908 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);
910 else if (effectnameindex == EFFECT_TE_BLOOD)
912 if (!cl_particles_blood.integer)
914 if (cl_particles_quake.integer)
915 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
918 static double bloodaccumulator = 0;
919 qboolean immediatebloodstain = true;
920 //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);
921 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
922 for (;bloodaccumulator > 0;bloodaccumulator--)
924 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, 1, 1);
925 if (immediatebloodstain && part)
927 immediatebloodstain = false;
928 CL_ImmediateBloodStain(part);
933 else if (effectnameindex == EFFECT_TE_SPARK)
934 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
935 else if (effectnameindex == EFFECT_TE_PLASMABURN)
937 // plasma scorch mark
938 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
939 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
940 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
942 else if (effectnameindex == EFFECT_TE_GUNSHOT)
944 if (cl_particles_bulletimpacts.integer)
946 if (cl_particles_quake.integer)
947 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
950 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
951 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
952 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, 1, 1);
956 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
957 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
959 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
961 if (cl_particles_bulletimpacts.integer)
963 if (cl_particles_quake.integer)
964 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
967 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
968 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
969 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, 1, 1);
973 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
974 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
975 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);
977 else if (effectnameindex == EFFECT_TE_EXPLOSION)
979 CL_ParticleExplosion(center);
980 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);
982 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
984 CL_ParticleExplosion(center);
985 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);
987 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
989 if (cl_particles_quake.integer)
992 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
995 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, 1, 1);
997 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, 1, 1);
1001 CL_ParticleExplosion(center);
1002 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);
1004 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1005 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);
1006 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1008 count *= cl_particles_quality.value;
1010 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, 1, 1);
1012 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1014 float i, j, inc, vel;
1017 inc = 8 / cl_particles_quality.value;
1018 for (i = -128;i < 128;i += inc)
1020 for (j = -128;j < 128;j += inc)
1022 dir[0] = j + lhrandom(0, inc);
1023 dir[1] = i + lhrandom(0, inc);
1025 org[0] = center[0] + dir[0];
1026 org[1] = center[1] + dir[1];
1027 org[2] = center[2] + lhrandom(0, 64);
1028 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1029 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, 1, 1);
1033 else if (effectnameindex == EFFECT_TE_TELEPORT)
1035 float i, j, k, inc, vel;
1038 if (cl_particles_quake.integer)
1039 inc = 4 / cl_particles_quality.value;
1041 inc = 8 / cl_particles_quality.value;
1042 for (i = -16;i < 16;i += inc)
1044 for (j = -16;j < 16;j += inc)
1046 for (k = -24;k < 32;k += inc)
1048 VectorSet(dir, i*8, j*8, k*8);
1049 VectorNormalize(dir);
1050 vel = lhrandom(50, 113);
1051 if (cl_particles_quake.integer)
1052 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, 1, 1);
1054 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, 1, 1);
1058 if (!cl_particles_quake.integer)
1059 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, 1, 1);
1060 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);
1062 else if (effectnameindex == EFFECT_TE_TEI_G3)
1063 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, 1, 1);
1064 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1066 if (cl_particles_smoke.integer)
1068 count *= 0.25f * cl_particles_quality.value;
1070 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, 1, 1);
1073 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1075 CL_ParticleExplosion(center);
1076 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);
1078 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1081 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1082 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1083 if (cl_particles_smoke.integer)
1084 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1085 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, 1, 1);
1086 if (cl_particles_sparks.integer)
1087 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1088 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, 1, 1);
1089 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);
1091 else if (effectnameindex == EFFECT_EF_FLAME)
1093 count *= 300 * cl_particles_quality.value;
1095 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, 1, 1);
1096 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);
1098 else if (effectnameindex == EFFECT_EF_STARDUST)
1100 count *= 200 * cl_particles_quality.value;
1102 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, 1, 1);
1103 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);
1105 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1109 int smoke, blood, bubbles, r, color;
1111 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1114 Vector4Set(light, 0, 0, 0, 0);
1116 if (effectnameindex == EFFECT_TR_ROCKET)
1117 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1118 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1120 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1121 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1123 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1125 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1126 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1130 matrix4x4_t tempmatrix;
1131 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1132 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);
1133 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1137 if (!spawnparticles)
1140 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1143 VectorSubtract(originmaxs, originmins, dir);
1144 len = VectorNormalizeLength(dir);
1147 dec = -ent->persistent.trail_time;
1148 ent->persistent.trail_time += len;
1149 if (ent->persistent.trail_time < 0.01f)
1152 // if we skip out, leave it reset
1153 ent->persistent.trail_time = 0.0f;
1158 // advance into this frame to reach the first puff location
1159 VectorMA(originmins, dec, dir, pos);
1162 smoke = cl_particles.integer && cl_particles_smoke.integer;
1163 blood = cl_particles.integer && cl_particles_blood.integer;
1164 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1165 qd = 1.0f / cl_particles_quality.value;
1172 if (effectnameindex == EFFECT_TR_BLOOD)
1174 if (cl_particles_quake.integer)
1176 color = particlepalette[67 + (rand()&3)];
1177 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, 1, 1);
1182 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, 1, 1);
1185 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1187 if (cl_particles_quake.integer)
1190 color = particlepalette[67 + (rand()&3)];
1191 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, 1, 1);
1196 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, 1, 1);
1202 if (effectnameindex == EFFECT_TR_ROCKET)
1204 if (cl_particles_quake.integer)
1207 color = particlepalette[ramp3[r]];
1208 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, 1, 1);
1212 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, 1, 1);
1213 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, 1, 1);
1216 else if (effectnameindex == EFFECT_TR_GRENADE)
1218 if (cl_particles_quake.integer)
1221 color = particlepalette[ramp3[r]];
1222 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, 1, 1);
1226 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, 1, 1);
1229 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1231 if (cl_particles_quake.integer)
1234 color = particlepalette[52 + (rand()&7)];
1235 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, 1, 1);
1236 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, 1, 1);
1238 else if (gamemode == GAME_GOODVSBAD2)
1241 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, 1, 1);
1245 color = particlepalette[20 + (rand()&7)];
1246 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, 1, 1);
1249 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1251 if (cl_particles_quake.integer)
1254 color = particlepalette[230 + (rand()&7)];
1255 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, 1, 1);
1256 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, 1, 1);
1260 color = particlepalette[226 + (rand()&7)];
1261 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, 1, 1);
1264 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1266 if (cl_particles_quake.integer)
1268 color = particlepalette[152 + (rand()&3)];
1269 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, 1, 1);
1271 else if (gamemode == GAME_GOODVSBAD2)
1274 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, 1, 1);
1276 else if (gamemode == GAME_PRYDON)
1279 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, 1, 1);
1282 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, 1, 1);
1284 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1287 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, 1, 1);
1289 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1292 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, 1, 1);
1294 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1295 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, 1, 1);
1299 if (effectnameindex == EFFECT_TR_ROCKET)
1300 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, 1, 1);
1301 else if (effectnameindex == EFFECT_TR_GRENADE)
1302 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, 1, 1);
1304 // advance to next time and position
1307 VectorMA (pos, dec, dir, pos);
1310 ent->persistent.trail_time = len;
1313 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1316 // this is also called on point effects with spawndlight = true and
1317 // spawnparticles = true
1318 // it is called CL_ParticleTrail because most code does not want to supply
1319 // these parameters, only trail handling does
1320 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)
1322 qboolean found = false;
1323 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1325 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1326 return; // no such effect
1328 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1330 int effectinfoindex;
1333 particleeffectinfo_t *info;
1340 qboolean underwater;
1341 qboolean immediatebloodstain;
1343 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1344 VectorLerp(originmins, 0.5, originmaxs, center);
1345 supercontents = CL_PointSuperContents(center);
1346 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1347 VectorSubtract(originmaxs, originmins, traildir);
1348 traillen = VectorLength(traildir);
1349 VectorNormalize(traildir);
1350 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1352 if (info->effectnameindex == effectnameindex)
1355 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1357 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1360 // spawn a dlight if requested
1361 if (info->lightradiusstart > 0 && spawndlight)
1363 matrix4x4_t tempmatrix;
1364 if (info->trailspacing > 0)
1365 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1367 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1368 if (info->lighttime > 0 && info->lightradiusfade > 0)
1370 // light flash (explosion, etc)
1371 // called when effect starts
1372 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);
1377 // called by CL_LinkNetworkEntity
1378 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1379 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);
1380 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1384 if (!spawnparticles)
1389 if (info->tex[1] > info->tex[0])
1391 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1392 tex = min(tex, info->tex[1] - 1);
1394 if(info->staintex[0] < 0)
1395 staintex = info->staintex[0];
1398 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1399 staintex = min(staintex, info->staintex[1] - 1);
1401 if (info->particletype == pt_decal)
1402 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]);
1403 else if (info->orientation == PARTICLE_HBEAM)
1404 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, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]));
1407 if (!cl_particles.integer)
1409 switch (info->particletype)
1411 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1412 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1413 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1414 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1415 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1416 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1419 VectorCopy(originmins, trailpos);
1420 if (info->trailspacing > 0)
1422 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1423 trailstep = info->trailspacing / cl_particles_quality.value;
1424 immediatebloodstain = false;
1428 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1430 immediatebloodstain = info->particletype == pt_blood || staintex;
1432 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1433 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1435 if (info->tex[1] > info->tex[0])
1437 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1438 tex = min(tex, info->tex[1] - 1);
1442 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1443 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1444 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1447 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, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]));
1448 if (immediatebloodstain && part)
1450 immediatebloodstain = false;
1451 CL_ImmediateBloodStain(part);
1454 VectorMA(trailpos, trailstep, traildir, trailpos);
1461 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1464 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)
1466 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1474 void CL_EntityParticles (const entity_t *ent)
1477 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1478 static vec3_t avelocities[NUMVERTEXNORMALS];
1479 if (!cl_particles.integer) return;
1480 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1482 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1484 if (!avelocities[0][0])
1485 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1486 avelocities[0][i] = lhrandom(0, 2.55);
1488 for (i = 0;i < NUMVERTEXNORMALS;i++)
1490 yaw = cl.time * avelocities[i][0];
1491 pitch = cl.time * avelocities[i][1];
1492 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1493 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1494 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1495 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, 1, 1);
1500 void CL_ReadPointFile_f (void)
1502 vec3_t org, leakorg;
1504 char *pointfile = NULL, *pointfilepos, *t, tchar;
1505 char name[MAX_OSPATH];
1510 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1511 strlcat (name, ".pts", sizeof (name));
1512 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1515 Con_Printf("Could not open %s\n", name);
1519 Con_Printf("Reading %s...\n", name);
1520 VectorClear(leakorg);
1523 pointfilepos = pointfile;
1524 while (*pointfilepos)
1526 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1531 while (*t && *t != '\n' && *t != '\r')
1535 #if _MSC_VER >= 1400
1536 #define sscanf sscanf_s
1538 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1544 VectorCopy(org, leakorg);
1547 if (cl.num_particles < cl.max_particles - 3)
1550 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, 1, 1);
1553 Mem_Free(pointfile);
1554 VectorCopy(leakorg, org);
1555 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1557 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, 1, 1);
1558 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, 1, 1);
1559 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, 1, 1);
1564 CL_ParseParticleEffect
1566 Parse an effect out of the server message
1569 void CL_ParseParticleEffect (void)
1572 int i, count, msgcount, color;
1574 MSG_ReadVector(org, cls.protocol);
1575 for (i=0 ; i<3 ; i++)
1576 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1577 msgcount = MSG_ReadByte ();
1578 color = MSG_ReadByte ();
1580 if (msgcount == 255)
1585 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1590 CL_ParticleExplosion
1594 void CL_ParticleExplosion (const vec3_t org)
1600 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1601 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1603 if (cl_particles_quake.integer)
1605 for (i = 0;i < 1024;i++)
1611 color = particlepalette[ramp1[r]];
1612 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, 1, 1);
1616 color = particlepalette[ramp2[r]];
1617 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, 1, 1);
1623 i = CL_PointSuperContents(org);
1624 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1626 if (cl_particles.integer && cl_particles_bubbles.integer)
1627 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1628 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, 1, 1);
1632 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1634 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1638 for (k = 0;k < 16;k++)
1641 VectorMA(org, 128, v2, v);
1642 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1643 if (trace.fraction >= 0.1)
1646 VectorSubtract(trace.endpos, org, v2);
1647 VectorScale(v2, 2.0f, v2);
1648 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, 1, 1);
1654 if (cl_particles_explosions_shell.integer)
1655 R_NewExplosion(org);
1660 CL_ParticleExplosion2
1664 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1667 if (!cl_particles.integer) return;
1669 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1671 k = particlepalette[colorStart + (i % colorLength)];
1672 if (cl_particles_quake.integer)
1673 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, 1, 1);
1675 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, 1, 1);
1679 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1681 if (cl_particles_sparks.integer)
1683 sparkcount *= cl_particles_quality.value;
1684 while(sparkcount-- > 0)
1685 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, 1, 1);
1689 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1691 if (cl_particles_smoke.integer)
1693 smokecount *= cl_particles_quality.value;
1694 while(smokecount-- > 0)
1695 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, 1, 1);
1699 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)
1702 if (!cl_particles.integer) return;
1704 count = (int)(count * cl_particles_quality.value);
1707 k = particlepalette[colorbase + (rand()&3)];
1708 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, 1, 1);
1712 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1715 float minz, maxz, lifetime = 30;
1716 if (!cl_particles.integer) return;
1717 if (dir[2] < 0) // falling
1719 minz = maxs[2] + dir[2] * 0.1;
1722 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1727 maxz = maxs[2] + dir[2] * 0.1;
1729 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1732 count = (int)(count * cl_particles_quality.value);
1737 if (!cl_particles_rain.integer) break;
1738 count *= 4; // ick, this should be in the mod or maps?
1742 k = particlepalette[colorbase + (rand()&3)];
1743 if (gamemode == GAME_GOODVSBAD2)
1744 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, 1, 1);
1746 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, 1, 1);
1750 if (!cl_particles_snow.integer) break;
1753 k = particlepalette[colorbase + (rand()&3)];
1754 if (gamemode == GAME_GOODVSBAD2)
1755 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, 1, 1);
1757 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, 1, 1);
1761 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1765 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1766 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1767 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1768 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1770 #define PARTICLETEXTURESIZE 64
1771 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1773 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1777 dz = 1 - (dx*dx+dy*dy);
1778 if (dz > 0) // it does hit the sphere
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 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1791 VectorNormalize(normal);
1792 dot = DotProduct(normal, light);
1793 if (dot > 0.5) // interior reflection
1794 f += ((dot * 2) - 1);
1795 else if (dot < -0.5) // exterior reflection
1796 f += ((dot * -2) - 1);
1798 f += 16; // just to give it a haze so you can see the outline
1799 f = bound(0, f, 255);
1800 return (unsigned char) f;
1806 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1807 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1809 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1810 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1811 *width = particlefontcellwidth;
1812 *height = particlefontcellheight;
1815 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1817 int basex, basey, w, h, y;
1818 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1819 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1820 Sys_Error("invalid particle texture size for autogenerating");
1821 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1822 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1825 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1828 float cx, cy, dx, dy, f, iradius;
1830 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1831 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1832 iradius = 1.0f / radius;
1833 alpha *= (1.0f / 255.0f);
1834 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1836 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1840 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1845 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1846 d[0] += (int)(f * (blue - d[0]));
1847 d[1] += (int)(f * (green - d[1]));
1848 d[2] += (int)(f * (red - d[2]));
1854 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1857 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1859 data[0] = bound(minb, data[0], maxb);
1860 data[1] = bound(ming, data[1], maxg);
1861 data[2] = bound(minr, data[2], maxr);
1865 void particletextureinvert(unsigned char *data)
1868 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1870 data[0] = 255 - data[0];
1871 data[1] = 255 - data[1];
1872 data[2] = 255 - data[2];
1876 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1877 static void R_InitBloodTextures (unsigned char *particletexturedata)
1880 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1881 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1884 for (i = 0;i < 8;i++)
1886 memset(data, 255, datasize);
1887 for (k = 0;k < 24;k++)
1888 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1889 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1890 particletextureinvert(data);
1891 setuptex(tex_bloodparticle[i], data, particletexturedata);
1895 for (i = 0;i < 8;i++)
1897 memset(data, 255, datasize);
1899 for (j = 1;j < 10;j++)
1900 for (k = min(j, m - 1);k < m;k++)
1901 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1902 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1903 particletextureinvert(data);
1904 setuptex(tex_blooddecal[i], data, particletexturedata);
1910 //uncomment this to make engine save out particle font to a tga file when run
1911 //#define DUMPPARTICLEFONT
1913 static void R_InitParticleTexture (void)
1915 int x, y, d, i, k, m;
1916 int basex, basey, w, h;
1920 fs_offset_t filesize;
1922 // a note: decals need to modulate (multiply) the background color to
1923 // properly darken it (stain), and they need to be able to alpha fade,
1924 // this is a very difficult challenge because it means fading to white
1925 // (no change to background) rather than black (darkening everything
1926 // behind the whole decal polygon), and to accomplish this the texture is
1927 // inverted (dark red blood on white background becomes brilliant cyan
1928 // and white on black background) so we can alpha fade it to black, then
1929 // we invert it again during the blendfunc to make it work...
1931 #ifndef DUMPPARTICLEFONT
1932 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1935 particlefonttexture = decalskinframe->base;
1936 // TODO maybe allow custom grid size?
1937 particlefontwidth = image_width;
1938 particlefontheight = image_height;
1939 particlefontcellwidth = image_width / 8;
1940 particlefontcellheight = image_height / 8;
1941 particlefontcols = 8;
1942 particlefontrows = 8;
1947 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1948 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1949 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1950 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1951 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1953 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1954 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1955 particlefontcols = 8;
1956 particlefontrows = 8;
1958 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1961 for (i = 0;i < 8;i++)
1963 memset(data, 255, datasize);
1966 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1967 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1969 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1971 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1972 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1974 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1975 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1977 d = (int)(d * (1-(dx*dx+dy*dy)));
1978 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1979 d = bound(0, d, 255);
1980 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
1987 setuptex(tex_smoke[i], data, particletexturedata);
1991 memset(data, 255, datasize);
1992 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1994 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1995 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1997 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1998 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1999 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2002 setuptex(tex_rainsplash, data, particletexturedata);
2005 memset(data, 255, datasize);
2006 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2008 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2009 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2011 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2012 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2013 d = bound(0, d, 255);
2014 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2017 setuptex(tex_particle, data, particletexturedata);
2020 memset(data, 255, datasize);
2021 light[0] = 1;light[1] = 1;light[2] = 1;
2022 VectorNormalize(light);
2023 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2025 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2026 // stretch upper half of bubble by +50% and shrink lower half by -50%
2027 // (this gives an elongated teardrop shape)
2029 dy = (dy - 0.5f) * 2.0f;
2031 dy = (dy - 0.5f) / 1.5f;
2032 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2034 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2035 // shrink bubble width to half
2037 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2040 setuptex(tex_raindrop, data, particletexturedata);
2043 memset(data, 255, datasize);
2044 light[0] = 1;light[1] = 1;light[2] = 1;
2045 VectorNormalize(light);
2046 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2048 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2049 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2051 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2052 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2055 setuptex(tex_bubble, data, particletexturedata);
2057 // Blood particles and blood decals
2058 R_InitBloodTextures (particletexturedata);
2061 for (i = 0;i < 8;i++)
2063 memset(data, 255, datasize);
2064 for (k = 0;k < 12;k++)
2065 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2066 for (k = 0;k < 3;k++)
2067 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2068 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2069 particletextureinvert(data);
2070 setuptex(tex_bulletdecal[i], data, particletexturedata);
2073 #ifdef DUMPPARTICLEFONT
2074 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2077 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2078 particlefonttexture = decalskinframe->base;
2080 Mem_Free(particletexturedata);
2085 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2087 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2088 particletexture[i].texture = particlefonttexture;
2089 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2090 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2091 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2092 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2095 #ifndef DUMPPARTICLEFONT
2096 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2097 if (!particletexture[tex_beam].texture)
2100 unsigned char noise3[64][64], data2[64][16][4];
2102 fractalnoise(&noise3[0][0], 64, 4);
2104 for (y = 0;y < 64;y++)
2106 dy = (y - 0.5f*64) / (64*0.5f-1);
2107 for (x = 0;x < 16;x++)
2109 dx = (x - 0.5f*16) / (16*0.5f-2);
2110 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2111 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2112 data2[y][x][3] = 255;
2116 #ifdef DUMPPARTICLEFONT
2117 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2119 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2121 particletexture[tex_beam].s1 = 0;
2122 particletexture[tex_beam].t1 = 0;
2123 particletexture[tex_beam].s2 = 1;
2124 particletexture[tex_beam].t2 = 1;
2126 // now load an texcoord/texture override file
2127 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2134 if(!COM_ParseToken_Simple(&bufptr, true, false))
2136 if(!strcmp(com_token, "\n"))
2137 continue; // empty line
2138 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2139 particletexture[i].texture = particlefonttexture;
2141 if (!COM_ParseToken_Simple(&bufptr, true, false))
2143 if (!strcmp(com_token, "\n"))
2145 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2148 particletexture[i].s1 = atof(com_token);
2150 if (!COM_ParseToken_Simple(&bufptr, true, false))
2152 if (!strcmp(com_token, "\n"))
2154 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2157 particletexture[i].t1 = atof(com_token);
2159 if (!COM_ParseToken_Simple(&bufptr, true, false))
2161 if (!strcmp(com_token, "\n"))
2163 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2166 particletexture[i].s2 = atof(com_token);
2168 if (!COM_ParseToken_Simple(&bufptr, true, false))
2170 if (!strcmp(com_token, "\n"))
2172 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2175 particletexture[i].t2 = atof(com_token);
2181 static void r_part_start(void)
2184 // generate particlepalette for convenience from the main one
2185 for (i = 0;i < 256;i++)
2186 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2187 particletexturepool = R_AllocTexturePool();
2188 R_InitParticleTexture ();
2189 CL_Particles_LoadEffectInfo();
2192 static void r_part_shutdown(void)
2194 R_FreeTexturePool(&particletexturepool);
2197 static void r_part_newmap(void)
2200 R_SkinFrame_MarkUsed(decalskinframe);
2201 CL_Particles_LoadEffectInfo();
2204 #define BATCHSIZE 256
2205 unsigned short particle_elements[BATCHSIZE*6];
2206 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2208 void R_Particles_Init (void)
2211 for (i = 0;i < BATCHSIZE;i++)
2213 particle_elements[i*6+0] = i*4+0;
2214 particle_elements[i*6+1] = i*4+1;
2215 particle_elements[i*6+2] = i*4+2;
2216 particle_elements[i*6+3] = i*4+0;
2217 particle_elements[i*6+4] = i*4+2;
2218 particle_elements[i*6+5] = i*4+3;
2221 Cvar_RegisterVariable(&r_drawparticles);
2222 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2223 Cvar_RegisterVariable(&r_drawdecals);
2224 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2225 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2228 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2230 int surfacelistindex;
2232 float *v3f, *t2f, *c4f;
2233 particletexture_t *tex;
2234 float right[3], up[3], size, ca;
2235 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2236 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2238 RSurf_ActiveWorldEntity();
2240 r_refdef.stats.drawndecals += numsurfaces;
2241 R_Mesh_ResetTextureState();
2242 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2243 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2244 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2245 GL_DepthMask(false);
2246 GL_DepthRange(0, 1);
2247 GL_PolygonOffset(0, 0);
2249 GL_CullFace(GL_NONE);
2251 // generate all the vertices at once
2252 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2254 d = cl.decals + surfacelist[surfacelistindex];
2257 c4f = particle_color4f + 16*surfacelistindex;
2258 ca = d->alpha * alphascale;
2259 // ensure alpha multiplier saturates properly
2260 if (ca > 1.0f / 256.0f)
2262 if (r_refdef.fogenabled)
2263 ca *= RSurf_FogVertex(d->org);
2264 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2265 Vector4Copy(c4f, c4f + 4);
2266 Vector4Copy(c4f, c4f + 8);
2267 Vector4Copy(c4f, c4f + 12);
2269 // calculate vertex positions
2270 size = d->size * cl_particles_size.value;
2271 VectorVectors(d->normal, right, up);
2272 VectorScale(right, size, right);
2273 VectorScale(up, size, up);
2274 v3f = particle_vertex3f + 12*surfacelistindex;
2275 v3f[ 0] = d->org[0] - right[0] - up[0];
2276 v3f[ 1] = d->org[1] - right[1] - up[1];
2277 v3f[ 2] = d->org[2] - right[2] - up[2];
2278 v3f[ 3] = d->org[0] - right[0] + up[0];
2279 v3f[ 4] = d->org[1] - right[1] + up[1];
2280 v3f[ 5] = d->org[2] - right[2] + up[2];
2281 v3f[ 6] = d->org[0] + right[0] + up[0];
2282 v3f[ 7] = d->org[1] + right[1] + up[1];
2283 v3f[ 8] = d->org[2] + right[2] + up[2];
2284 v3f[ 9] = d->org[0] + right[0] - up[0];
2285 v3f[10] = d->org[1] + right[1] - up[1];
2286 v3f[11] = d->org[2] + right[2] - up[2];
2288 // calculate texcoords
2289 tex = &particletexture[d->texnum];
2290 t2f = particle_texcoord2f + 8*surfacelistindex;
2291 t2f[0] = tex->s1;t2f[1] = tex->t2;
2292 t2f[2] = tex->s1;t2f[3] = tex->t1;
2293 t2f[4] = tex->s2;t2f[5] = tex->t1;
2294 t2f[6] = tex->s2;t2f[7] = tex->t2;
2297 // now render the decals all at once
2298 // (this assumes they all use one particle font texture!)
2299 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2300 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2301 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2304 void R_DrawDecals (void)
2307 int drawdecals = r_drawdecals.integer;
2312 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2314 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2315 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2317 // LordHavoc: early out conditions
2321 decalfade = frametime * 256 / cl_decals_fadetime.value;
2322 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2323 drawdist2 = drawdist2*drawdist2;
2325 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2327 if (!decal->typeindex)
2330 if (killsequence - decal->decalsequence > 0)
2333 if (cl.time > decal->time2 + cl_decals_time.value)
2335 decal->alpha -= decalfade;
2336 if (decal->alpha <= 0)
2342 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2344 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2345 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2351 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2357 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))
2358 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2361 decal->typeindex = 0;
2362 if (cl.free_decal > i)
2366 // reduce cl.num_decals if possible
2367 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2370 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2372 decal_t *olddecals = cl.decals;
2373 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2374 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2375 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2376 Mem_Free(olddecals);
2379 r_refdef.stats.totaldecals = cl.num_decals;
2382 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2384 int surfacelistindex;
2385 int batchstart, batchcount;
2386 const particle_t *p;
2388 rtexture_t *texture;
2389 float *v3f, *t2f, *c4f;
2390 particletexture_t *tex;
2391 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2392 float ambient[3], diffuse[3], diffusenormal[3];
2393 vec4_t colormultiplier;
2395 RSurf_ActiveWorldEntity();
2397 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));
2399 r_refdef.stats.particles += numsurfaces;
2400 R_Mesh_ResetTextureState();
2401 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2402 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2403 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2404 GL_DepthMask(false);
2405 GL_DepthRange(0, 1);
2406 GL_PolygonOffset(0, 0);
2408 GL_CullFace(GL_NONE);
2410 // first generate all the vertices at once
2411 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2413 p = cl.particles + surfacelist[surfacelistindex];
2415 blendmode = p->blendmode;
2419 case PBLEND_INVALID:
2421 alpha = p->alpha * colormultiplier[3];
2422 // ensure alpha multiplier saturates properly
2425 // additive and modulate can just fade out in fog (this is correct)
2426 if (r_refdef.fogenabled)
2427 alpha *= RSurf_FogVertex(p->org);
2428 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2429 alpha *= 1.0f / 256.0f;
2430 c4f[0] = p->color[0] * alpha;
2431 c4f[1] = p->color[1] * alpha;
2432 c4f[2] = p->color[2] * alpha;
2436 alpha = p->alpha * colormultiplier[3];
2437 // ensure alpha multiplier saturates properly
2440 // additive and modulate can just fade out in fog (this is correct)
2441 if (r_refdef.fogenabled)
2442 alpha *= RSurf_FogVertex(p->org);
2443 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2444 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2445 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2446 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2450 c4f[0] = p->color[0] * colormultiplier[0];
2451 c4f[1] = p->color[1] * colormultiplier[1];
2452 c4f[2] = p->color[2] * colormultiplier[2];
2453 c4f[3] = p->alpha * colormultiplier[3];
2454 // note: lighting is not cheap!
2455 if (particletype[p->typeindex].lighting)
2457 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2458 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2459 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2460 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2462 // mix in the fog color
2463 if (r_refdef.fogenabled)
2465 fog = RSurf_FogVertex(p->org);
2467 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2468 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2469 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2473 // copy the color into the other three vertices
2474 Vector4Copy(c4f, c4f + 4);
2475 Vector4Copy(c4f, c4f + 8);
2476 Vector4Copy(c4f, c4f + 12);
2478 size = p->size * cl_particles_size.value;
2479 tex = &particletexture[p->texnum];
2480 switch(p->orientation)
2482 case PARTICLE_INVALID:
2483 case PARTICLE_BILLBOARD:
2484 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2485 VectorScale(r_refdef.view.up, size, up);
2486 v3f[ 0] = p->org[0] - right[0] - up[0];
2487 v3f[ 1] = p->org[1] - right[1] - up[1];
2488 v3f[ 2] = p->org[2] - right[2] - up[2];
2489 v3f[ 3] = p->org[0] - right[0] + up[0];
2490 v3f[ 4] = p->org[1] - right[1] + up[1];
2491 v3f[ 5] = p->org[2] - right[2] + up[2];
2492 v3f[ 6] = p->org[0] + right[0] + up[0];
2493 v3f[ 7] = p->org[1] + right[1] + up[1];
2494 v3f[ 8] = p->org[2] + right[2] + up[2];
2495 v3f[ 9] = p->org[0] + right[0] - up[0];
2496 v3f[10] = p->org[1] + right[1] - up[1];
2497 v3f[11] = p->org[2] + right[2] - up[2];
2498 t2f[0] = tex->s1;t2f[1] = tex->t2;
2499 t2f[2] = tex->s1;t2f[3] = tex->t1;
2500 t2f[4] = tex->s2;t2f[5] = tex->t1;
2501 t2f[6] = tex->s2;t2f[7] = tex->t2;
2503 case PARTICLE_ORIENTED_DOUBLESIDED:
2504 VectorVectors(p->vel, right, up);
2505 VectorScale(right, size * p->stretch, right);
2506 VectorScale(up, size, up);
2507 v3f[ 0] = p->org[0] - right[0] - up[0];
2508 v3f[ 1] = p->org[1] - right[1] - up[1];
2509 v3f[ 2] = p->org[2] - right[2] - up[2];
2510 v3f[ 3] = p->org[0] - right[0] + up[0];
2511 v3f[ 4] = p->org[1] - right[1] + up[1];
2512 v3f[ 5] = p->org[2] - right[2] + up[2];
2513 v3f[ 6] = p->org[0] + right[0] + up[0];
2514 v3f[ 7] = p->org[1] + right[1] + up[1];
2515 v3f[ 8] = p->org[2] + right[2] + up[2];
2516 v3f[ 9] = p->org[0] + right[0] - up[0];
2517 v3f[10] = p->org[1] + right[1] - up[1];
2518 v3f[11] = p->org[2] + right[2] - up[2];
2519 t2f[0] = tex->s1;t2f[1] = tex->t2;
2520 t2f[2] = tex->s1;t2f[3] = tex->t1;
2521 t2f[4] = tex->s2;t2f[5] = tex->t1;
2522 t2f[6] = tex->s2;t2f[7] = tex->t2;
2524 case PARTICLE_SPARK:
2525 len = VectorLength(p->vel);
2526 VectorNormalize2(p->vel, up);
2527 lenfactor = p->stretch * 0.04 * len;
2528 if(lenfactor < size * 0.5)
2529 lenfactor = size * 0.5;
2530 VectorMA(p->org, -lenfactor, up, v);
2531 VectorMA(p->org, lenfactor, up, up2);
2532 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2533 t2f[0] = tex->s1;t2f[1] = tex->t2;
2534 t2f[2] = tex->s1;t2f[3] = tex->t1;
2535 t2f[4] = tex->s2;t2f[5] = tex->t1;
2536 t2f[6] = tex->s2;t2f[7] = tex->t2;
2538 case PARTICLE_VBEAM:
2539 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2540 VectorSubtract(p->vel, p->org, up);
2541 VectorNormalize(up);
2542 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2543 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2544 t2f[0] = tex->s2;t2f[1] = v[0];
2545 t2f[2] = tex->s1;t2f[3] = v[0];
2546 t2f[4] = tex->s1;t2f[5] = v[1];
2547 t2f[6] = tex->s2;t2f[7] = v[1];
2549 case PARTICLE_HBEAM:
2550 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2551 VectorSubtract(p->vel, p->org, up);
2552 VectorNormalize(up);
2553 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2554 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2555 t2f[0] = v[0];t2f[1] = tex->t1;
2556 t2f[2] = v[0];t2f[3] = tex->t2;
2557 t2f[4] = v[1];t2f[5] = tex->t2;
2558 t2f[6] = v[1];t2f[7] = tex->t1;
2563 // now render batches of particles based on blendmode and texture
2564 blendmode = PBLEND_INVALID;
2568 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2570 p = cl.particles + surfacelist[surfacelistindex];
2572 if (blendmode != p->blendmode)
2574 blendmode = p->blendmode;
2578 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2580 case PBLEND_INVALID:
2582 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2585 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2589 if (texture != particletexture[p->texnum].texture)
2591 texture = particletexture[p->texnum].texture;
2592 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2595 // iterate until we find a change in settings
2596 batchstart = surfacelistindex++;
2597 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2599 p = cl.particles + surfacelist[surfacelistindex];
2600 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2604 batchcount = surfacelistindex - batchstart;
2605 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2609 void R_DrawParticles (void)
2612 int drawparticles = r_drawparticles.integer;
2613 float minparticledist;
2615 float gravity, frametime, f, dist, oldorg[3];
2621 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2622 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2624 // LordHavoc: early out conditions
2625 if (!cl.num_particles)
2628 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2629 gravity = frametime * cl.movevars_gravity;
2630 update = frametime > 0;
2631 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2632 drawdist2 = drawdist2*drawdist2;
2634 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2638 if (cl.free_particle > i)
2639 cl.free_particle = i;
2645 if (p->delayedspawn > cl.time)
2647 p->delayedspawn = 0;
2649 p->size += p->sizeincrease * frametime;
2650 p->alpha -= p->alphafade * frametime;
2652 if (p->alpha <= 0 || p->die <= cl.time)
2655 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2657 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2659 if (p->typeindex == pt_blood)
2660 p->size += frametime * 8;
2662 p->vel[2] -= p->gravity * gravity;
2663 f = 1.0f - min(p->liquidfriction * frametime, 1);
2664 VectorScale(p->vel, f, p->vel);
2668 p->vel[2] -= p->gravity * gravity;
2671 f = 1.0f - min(p->airfriction * frametime, 1);
2672 VectorScale(p->vel, f, p->vel);
2676 VectorCopy(p->org, oldorg);
2677 VectorMA(p->org, frametime, p->vel, p->org);
2678 if (p->bounce && cl.time >= p->delayedcollisions)
2680 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);
2681 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2682 // or if the trace hit something flagged as NOIMPACT
2683 // then remove the particle
2684 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2686 VectorCopy(trace.endpos, p->org);
2687 // react if the particle hit something
2688 if (trace.fraction < 1)
2690 VectorCopy(trace.endpos, p->org);
2692 if (p->staintexnum >= 0)
2694 // blood - splash on solid
2695 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2698 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2699 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2700 if (cl_decals.integer)
2702 // create a decal for the blood splat
2703 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2708 if (p->typeindex == pt_blood)
2710 // blood - splash on solid
2711 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2713 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2715 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)));
2716 if (cl_decals.integer)
2718 // create a decal for the blood splat
2719 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);
2724 else if (p->bounce < 0)
2726 // bounce -1 means remove on impact
2731 // anything else - bounce off solid
2732 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2733 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2734 if (DotProduct(p->vel, p->vel) < 0.03)
2735 VectorClear(p->vel);
2741 if (p->typeindex != pt_static)
2743 switch (p->typeindex)
2745 case pt_entityparticle:
2746 // particle that removes itself after one rendered frame
2753 a = CL_PointSuperContents(p->org);
2754 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2758 a = CL_PointSuperContents(p->org);
2759 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2763 a = CL_PointSuperContents(p->org);
2764 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2768 if (cl.time > p->time2)
2771 p->time2 = cl.time + (rand() & 3) * 0.1;
2772 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2773 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2775 a = CL_PointSuperContents(p->org);
2776 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2784 else if (p->delayedspawn)
2788 // don't render particles too close to the view (they chew fillrate)
2789 // also don't render particles behind the view (useless)
2790 // further checks to cull to the frustum would be too slow here
2791 switch(p->typeindex)
2794 // beams have no culling
2795 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2798 if(cl_particles_visculling.integer)
2799 if (!r_refdef.viewcache.world_novis)
2800 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2802 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2804 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2807 // anything else just has to be in front of the viewer and visible at this distance
2808 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2809 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2816 if (cl.free_particle > i)
2817 cl.free_particle = i;
2820 // reduce cl.num_particles if possible
2821 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2824 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2826 particle_t *oldparticles = cl.particles;
2827 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2828 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2829 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2830 Mem_Free(oldparticles);