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 float rotate[4]; // min/max base angle, min/max rotation over time
122 particleeffectinfo_t;
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
127 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
129 static int particlepalette[256];
131 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
132 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
133 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
134 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
135 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
136 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
137 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
138 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
139 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
140 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
141 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
142 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
143 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
144 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
145 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
146 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
147 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
148 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
149 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
150 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
151 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
152 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
153 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
154 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
155 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
156 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
157 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
158 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
159 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
160 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
161 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
162 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
165 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
166 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
167 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
169 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
171 // particletexture_t is a rectangle in the particlefonttexture
172 typedef struct particletexture_s
175 float s1, t1, s2, t2;
179 static rtexturepool_t *particletexturepool;
180 static rtexture_t *particlefonttexture;
181 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
182 skinframe_t *decalskinframe;
184 // texture numbers in particle font
185 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
186 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
187 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
188 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
189 static const int tex_rainsplash = 32;
190 static const int tex_particle = 63;
191 static const int tex_bubble = 62;
192 static const int tex_raindrop = 61;
193 static const int tex_beam = 60;
195 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
196 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
197 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
198 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
199 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
200 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
201 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
202 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
203 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
204 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
205 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
206 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
207 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
208 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
209 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
210 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
211 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
212 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
213 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
214 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
215 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
216 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
217 cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"};
218 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
219 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
220 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
221 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
222 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
223 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)"};
224 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
225 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
226 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
229 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
235 particleeffectinfo_t *info = NULL;
236 const char *text = textstart;
238 effectinfoindex = -1;
239 for (linenumber = 1;;linenumber++)
242 for (arrayindex = 0;arrayindex < 16;arrayindex++)
243 argv[arrayindex][0] = 0;
246 if (!COM_ParseToken_Simple(&text, true, false))
248 if (!strcmp(com_token, "\n"))
252 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
258 #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;}
259 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
260 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
261 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
262 #define readfloat(var) checkparms(2);var = atof(argv[1])
263 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
264 if (!strcmp(argv[0], "effect"))
269 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
271 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
274 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
276 if (particleeffectname[effectnameindex][0])
278 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
283 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
287 // if we run out of names, abort
288 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
290 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
293 info = particleeffectinfo + effectinfoindex;
294 info->effectnameindex = effectnameindex;
295 info->particletype = pt_alphastatic;
296 info->blendmode = particletype[info->particletype].blendmode;
297 info->orientation = particletype[info->particletype].orientation;
298 info->tex[0] = tex_particle;
299 info->tex[1] = tex_particle;
300 info->color[0] = 0xFFFFFF;
301 info->color[1] = 0xFFFFFF;
305 info->alpha[1] = 256;
306 info->alpha[2] = 256;
307 info->time[0] = 9999;
308 info->time[1] = 9999;
309 VectorSet(info->lightcolor, 1, 1, 1);
310 info->lightshadow = true;
311 info->lighttime = 9999;
312 info->stretchfactor = 1;
313 info->staincolor[0] = (unsigned int)-1;
314 info->staincolor[1] = (unsigned int)-1;
315 info->staintex[0] = -1;
316 info->staintex[1] = -1;
317 info->stainalpha[0] = 1;
318 info->stainalpha[1] = 1;
319 info->stainsize[0] = 2;
320 info->stainsize[1] = 2;
322 info->rotate[1] = 360;
326 else if (info == NULL)
328 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
331 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
332 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
333 else if (!strcmp(argv[0], "type"))
336 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
337 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
338 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
339 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
340 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
341 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
342 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
343 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
344 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
345 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
346 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
347 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
348 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
349 info->blendmode = particletype[info->particletype].blendmode;
350 info->orientation = particletype[info->particletype].orientation;
352 else if (!strcmp(argv[0], "blend"))
355 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
356 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
357 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
358 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
360 else if (!strcmp(argv[0], "orientation"))
363 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
364 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
365 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
366 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
367 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
369 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
370 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
371 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
372 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
373 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
374 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
375 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
376 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
377 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
378 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
379 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
380 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
381 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
382 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
383 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
384 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
385 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
386 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
387 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
388 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
389 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
390 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
391 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
392 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
393 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
394 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
395 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
396 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
397 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
398 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; }
399 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
401 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
410 int CL_ParticleEffectIndexForName(const char *name)
413 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
414 if (!strcmp(particleeffectname[i], name))
419 const char *CL_ParticleEffectNameForIndex(int i)
421 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
423 return particleeffectname[i];
426 // MUST match effectnameindex_t in client.h
427 static const char *standardeffectnames[EFFECT_TOTAL] =
451 "TE_TEI_BIGEXPLOSION",
467 void CL_Particles_LoadEffectInfo(void)
471 unsigned char *filedata;
472 fs_offset_t filesize;
473 char filename[MAX_QPATH];
474 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
475 memset(particleeffectname, 0, sizeof(particleeffectname));
476 for (i = 0;i < EFFECT_TOTAL;i++)
477 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
478 for (filepass = 0;;filepass++)
481 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
482 else if (filepass == 1)
483 dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
486 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
489 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
499 void CL_ReadPointFile_f (void);
500 void CL_Particles_Init (void)
502 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)");
503 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
505 Cvar_RegisterVariable (&cl_particles);
506 Cvar_RegisterVariable (&cl_particles_quality);
507 Cvar_RegisterVariable (&cl_particles_alpha);
508 Cvar_RegisterVariable (&cl_particles_size);
509 Cvar_RegisterVariable (&cl_particles_quake);
510 Cvar_RegisterVariable (&cl_particles_blood);
511 Cvar_RegisterVariable (&cl_particles_blood_alpha);
512 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
513 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
514 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
515 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
516 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
517 Cvar_RegisterVariable (&cl_particles_explosions_shell);
518 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
519 Cvar_RegisterVariable (&cl_particles_rain);
520 Cvar_RegisterVariable (&cl_particles_snow);
521 Cvar_RegisterVariable (&cl_particles_smoke);
522 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
523 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
524 Cvar_RegisterVariable (&cl_particles_sparks);
525 Cvar_RegisterVariable (&cl_particles_bubbles);
526 Cvar_RegisterVariable (&cl_particles_visculling);
527 Cvar_RegisterVariable (&cl_particles_collisions);
528 Cvar_RegisterVariable (&cl_decals);
529 Cvar_RegisterVariable (&cl_decals_visculling);
530 Cvar_RegisterVariable (&cl_decals_time);
531 Cvar_RegisterVariable (&cl_decals_fadetime);
532 Cvar_RegisterVariable (&cl_decals_newsystem);
533 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
534 Cvar_RegisterVariable (&cl_decals_models);
535 Cvar_RegisterVariable (&cl_decals_bias);
536 Cvar_RegisterVariable (&cl_decals_max);
539 void CL_Particles_Shutdown (void)
543 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
544 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
546 // list of all 26 parameters:
547 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
548 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
549 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
550 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
551 // palpha - opacity of particle as 0-255 (can be more than 255)
552 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
553 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
554 // pgravity - how much effect gravity has on the particle (0-1)
555 // 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
556 // px,py,pz - starting origin of particle
557 // pvx,pvy,pvz - starting velocity of particle
558 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
559 // blendmode - one of the PBLEND_ values
560 // orientation - one of the PARTICLE_ values
561 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
562 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
563 // stainalpha: opacity of the stain as factor for alpha
564 // stainsize: size of the stain as factor for palpha
565 // angle: base rotation of the particle geometry around its center normal
566 // spin: rotation speed of the particle geometry around its center normal
567 particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin)
572 if (!cl_particles.integer)
574 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
575 if (cl.free_particle >= cl.max_particles)
578 lifetime = palpha / min(1, palphafade);
579 part = &cl.particles[cl.free_particle++];
580 if (cl.num_particles < cl.free_particle)
581 cl.num_particles = cl.free_particle;
582 memset(part, 0, sizeof(*part));
583 VectorCopy(sortorigin, part->sortorigin);
584 part->typeindex = ptypeindex;
585 part->blendmode = blendmode;
586 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
588 particletexture_t *tex = &particletexture[ptex];
589 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
590 part->orientation = PARTICLE_VBEAM;
592 part->orientation = PARTICLE_HBEAM;
595 part->orientation = orientation;
596 l2 = (int)lhrandom(0.5, 256.5);
598 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
599 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
600 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
601 part->staintexnum = staintex;
602 if(staincolor1 >= 0 && staincolor2 >= 0)
604 l2 = (int)lhrandom(0.5, 256.5);
606 if(blendmode == PBLEND_INVMOD)
608 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
609 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
610 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
614 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
615 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
616 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
618 if(r > 0xFF) r = 0xFF;
619 if(g > 0xFF) g = 0xFF;
620 if(b > 0xFF) b = 0xFF;
624 r = part->color[0]; // -1 is shorthand for stain = particle color
628 part->staincolor[0] = r;
629 part->staincolor[1] = g;
630 part->staincolor[2] = b;
631 part->stainalpha = palpha * stainalpha;
632 part->stainsize = psize * stainsize;
635 part->sizeincrease = psizeincrease;
636 part->alpha = palpha;
637 part->alphafade = palphafade;
638 part->gravity = pgravity;
639 part->bounce = pbounce;
640 part->stretch = stretch;
642 part->org[0] = px + originjitter * v[0];
643 part->org[1] = py + originjitter * v[1];
644 part->org[2] = pz + originjitter * v[2];
645 part->vel[0] = pvx + velocityjitter * v[0];
646 part->vel[1] = pvy + velocityjitter * v[1];
647 part->vel[2] = pvz + velocityjitter * v[2];
649 part->airfriction = pairfriction;
650 part->liquidfriction = pliquidfriction;
651 part->die = cl.time + lifetime;
652 // part->delayedcollisions = 0;
653 part->qualityreduction = pqualityreduction;
656 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
657 if (part->typeindex == pt_rain)
661 float lifetime = part->die - cl.time;
664 // turn raindrop into simple spark and create delayedspawn splash effect
665 part->typeindex = pt_spark;
667 VectorMA(part->org, lifetime, part->vel, endvec);
668 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
669 part->die = cl.time + lifetime * trace.fraction;
670 part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0);
673 part2->delayedspawn = part->die;
674 part2->die += part->die - cl.time;
675 for (i = rand() & 7;i < 10;i++)
677 part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
680 part2->delayedspawn = part->die;
681 part2->die += part->die - cl.time;
687 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
689 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
692 VectorMA(part->org, lifetime, part->vel, endvec);
693 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
694 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
701 static void CL_ImmediateBloodStain(particle_t *part)
706 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
707 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
709 VectorCopy(part->vel, v);
711 staintex = part->staintexnum;
712 R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize);
715 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
716 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
718 VectorCopy(part->vel, v);
720 staintex = tex_blooddecal[rand()&7];
721 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);
725 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
729 entity_render_t *ent = &cl.entities[hitent].render;
730 unsigned char color[3];
731 if (!cl_decals.integer)
733 if (!ent->allowdecals)
736 l2 = (int)lhrandom(0.5, 256.5);
738 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
739 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
740 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
742 if (cl_decals_newsystem.integer)
744 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);
748 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
749 if (cl.free_decal >= cl.max_decals)
751 decal = &cl.decals[cl.free_decal++];
752 if (cl.num_decals < cl.free_decal)
753 cl.num_decals = cl.free_decal;
754 memset(decal, 0, sizeof(*decal));
755 decal->decalsequence = cl.decalsequence++;
756 decal->typeindex = pt_decal;
757 decal->texnum = texnum;
758 VectorMA(org, cl_decals_bias.value, normal, decal->org);
759 VectorCopy(normal, decal->normal);
761 decal->alpha = alpha;
762 decal->time2 = cl.time;
763 decal->color[0] = color[0];
764 decal->color[1] = color[1];
765 decal->color[2] = color[2];
766 decal->owner = hitent;
767 decal->clusterindex = -1000; // no vis culling unless we're sure
770 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
771 decal->ownermodel = cl.entities[decal->owner].render.model;
772 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
773 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
777 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
779 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
781 decal->clusterindex = leaf->clusterindex;
786 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
789 float bestfrac, bestorg[3], bestnormal[3];
791 int besthitent = 0, hitent;
794 for (i = 0;i < 32;i++)
797 VectorMA(org, maxdist, org2, org2);
798 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
799 // take the closest trace result that doesn't end up hitting a NOMARKS
800 // surface (sky for example)
801 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
803 bestfrac = trace.fraction;
805 VectorCopy(trace.endpos, bestorg);
806 VectorCopy(trace.plane.normal, bestnormal);
810 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
813 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
814 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
815 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)
818 matrix4x4_t tempmatrix;
820 VectorLerp(originmins, 0.5, originmaxs, center);
821 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
822 if (effectnameindex == EFFECT_SVC_PARTICLE)
824 if (cl_particles.integer)
826 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
828 CL_ParticleExplosion(center);
829 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
830 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
833 count *= cl_particles_quality.value;
834 for (;count > 0;count--)
836 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
837 CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
842 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
843 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
844 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
845 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
846 else if (effectnameindex == EFFECT_TE_SPIKE)
848 if (cl_particles_bulletimpacts.integer)
850 if (cl_particles_quake.integer)
852 if (cl_particles_smoke.integer)
853 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
857 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
858 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
859 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
863 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
864 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
866 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
868 if (cl_particles_bulletimpacts.integer)
870 if (cl_particles_quake.integer)
872 if (cl_particles_smoke.integer)
873 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
877 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
878 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
879 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
883 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
884 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
885 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);
887 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
889 if (cl_particles_bulletimpacts.integer)
891 if (cl_particles_quake.integer)
893 if (cl_particles_smoke.integer)
894 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
898 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
899 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
900 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
904 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
905 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
907 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
909 if (cl_particles_bulletimpacts.integer)
911 if (cl_particles_quake.integer)
913 if (cl_particles_smoke.integer)
914 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
918 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
919 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
920 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
924 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
925 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
926 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);
928 else if (effectnameindex == EFFECT_TE_BLOOD)
930 if (!cl_particles_blood.integer)
932 if (cl_particles_quake.integer)
933 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
936 static double bloodaccumulator = 0;
937 qboolean immediatebloodstain = true;
938 //CL_NewParticle(center, pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
939 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
940 for (;bloodaccumulator > 0;bloodaccumulator--)
942 part = CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
943 if (immediatebloodstain && part)
945 immediatebloodstain = false;
946 CL_ImmediateBloodStain(part);
951 else if (effectnameindex == EFFECT_TE_SPARK)
952 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
953 else if (effectnameindex == EFFECT_TE_PLASMABURN)
955 // plasma scorch mark
956 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
957 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
958 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
960 else if (effectnameindex == EFFECT_TE_GUNSHOT)
962 if (cl_particles_bulletimpacts.integer)
964 if (cl_particles_quake.integer)
965 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
968 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
969 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
970 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
974 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
975 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
977 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
979 if (cl_particles_bulletimpacts.integer)
981 if (cl_particles_quake.integer)
982 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
985 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
986 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
987 CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
991 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
992 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
993 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);
995 else if (effectnameindex == EFFECT_TE_EXPLOSION)
997 CL_ParticleExplosion(center);
998 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);
1000 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1002 CL_ParticleExplosion(center);
1003 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);
1005 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1007 if (cl_particles_quake.integer)
1010 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1013 CL_NewParticle(center, pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1015 CL_NewParticle(center, pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1019 CL_ParticleExplosion(center);
1020 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);
1022 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1023 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);
1024 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1026 count *= cl_particles_quality.value;
1028 CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1030 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1032 float i, j, inc, vel;
1035 inc = 8 / cl_particles_quality.value;
1036 for (i = -128;i < 128;i += inc)
1038 for (j = -128;j < 128;j += inc)
1040 dir[0] = j + lhrandom(0, inc);
1041 dir[1] = i + lhrandom(0, inc);
1043 org[0] = center[0] + dir[0];
1044 org[1] = center[1] + dir[1];
1045 org[2] = center[2] + lhrandom(0, 64);
1046 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1047 CL_NewParticle(center, pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1051 else if (effectnameindex == EFFECT_TE_TELEPORT)
1053 float i, j, k, inc, vel;
1056 if (cl_particles_quake.integer)
1057 inc = 4 / cl_particles_quality.value;
1059 inc = 8 / cl_particles_quality.value;
1060 for (i = -16;i < 16;i += inc)
1062 for (j = -16;j < 16;j += inc)
1064 for (k = -24;k < 32;k += inc)
1066 VectorSet(dir, i*8, j*8, k*8);
1067 VectorNormalize(dir);
1068 vel = lhrandom(50, 113);
1069 if (cl_particles_quake.integer)
1070 CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1072 CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1076 if (!cl_particles_quake.integer)
1077 CL_NewParticle(center, pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1078 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);
1080 else if (effectnameindex == EFFECT_TE_TEI_G3)
1081 CL_NewParticle(center, pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0);
1082 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1084 if (cl_particles_smoke.integer)
1086 count *= 0.25f * cl_particles_quality.value;
1088 CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1091 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1093 CL_ParticleExplosion(center);
1094 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);
1096 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1099 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1100 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1101 if (cl_particles_smoke.integer)
1102 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1103 CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1104 if (cl_particles_sparks.integer)
1105 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1106 CL_NewParticle(center, pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1107 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);
1109 else if (effectnameindex == EFFECT_EF_FLAME)
1111 count *= 300 * cl_particles_quality.value;
1113 CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1114 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);
1116 else if (effectnameindex == EFFECT_EF_STARDUST)
1118 count *= 200 * cl_particles_quality.value;
1120 CL_NewParticle(center, pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1121 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);
1123 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1127 int smoke, blood, bubbles, r, color;
1129 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1132 Vector4Set(light, 0, 0, 0, 0);
1134 if (effectnameindex == EFFECT_TR_ROCKET)
1135 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1136 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1138 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1139 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1141 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1143 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1144 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1148 matrix4x4_t tempmatrix;
1149 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1150 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);
1151 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1155 if (!spawnparticles)
1158 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1161 VectorSubtract(originmaxs, originmins, dir);
1162 len = VectorNormalizeLength(dir);
1165 dec = -ent->persistent.trail_time;
1166 ent->persistent.trail_time += len;
1167 if (ent->persistent.trail_time < 0.01f)
1170 // if we skip out, leave it reset
1171 ent->persistent.trail_time = 0.0f;
1176 // advance into this frame to reach the first puff location
1177 VectorMA(originmins, dec, dir, pos);
1180 smoke = cl_particles.integer && cl_particles_smoke.integer;
1181 blood = cl_particles.integer && cl_particles_blood.integer;
1182 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1183 qd = 1.0f / cl_particles_quality.value;
1190 if (effectnameindex == EFFECT_TR_BLOOD)
1192 if (cl_particles_quake.integer)
1194 color = particlepalette[67 + (rand()&3)];
1195 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1200 CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1203 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1205 if (cl_particles_quake.integer)
1208 color = particlepalette[67 + (rand()&3)];
1209 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1214 CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1220 if (effectnameindex == EFFECT_TR_ROCKET)
1222 if (cl_particles_quake.integer)
1225 color = particlepalette[ramp3[r]];
1226 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1230 CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1231 CL_NewParticle(center, pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1234 else if (effectnameindex == EFFECT_TR_GRENADE)
1236 if (cl_particles_quake.integer)
1239 color = particlepalette[ramp3[r]];
1240 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1244 CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1247 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1249 if (cl_particles_quake.integer)
1252 color = particlepalette[52 + (rand()&7)];
1253 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1254 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1256 else if (gamemode == GAME_GOODVSBAD2)
1259 CL_NewParticle(center, pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1263 color = particlepalette[20 + (rand()&7)];
1264 CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1267 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1269 if (cl_particles_quake.integer)
1272 color = particlepalette[230 + (rand()&7)];
1273 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1274 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1278 color = particlepalette[226 + (rand()&7)];
1279 CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1282 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1284 if (cl_particles_quake.integer)
1286 color = particlepalette[152 + (rand()&3)];
1287 CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1289 else if (gamemode == GAME_GOODVSBAD2)
1292 CL_NewParticle(center, pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1294 else if (gamemode == GAME_PRYDON)
1297 CL_NewParticle(center, pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1300 CL_NewParticle(center, pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1302 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1305 CL_NewParticle(center, pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1307 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1310 CL_NewParticle(center, pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1312 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1313 CL_NewParticle(center, pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1317 if (effectnameindex == EFFECT_TR_ROCKET)
1318 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1319 else if (effectnameindex == EFFECT_TR_GRENADE)
1320 CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1322 // advance to next time and position
1325 VectorMA (pos, dec, dir, pos);
1328 ent->persistent.trail_time = len;
1331 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1334 // this is also called on point effects with spawndlight = true and
1335 // spawnparticles = true
1336 // it is called CL_ParticleTrail because most code does not want to supply
1337 // these parameters, only trail handling does
1338 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)
1340 qboolean found = false;
1341 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1343 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1344 return; // no such effect
1346 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1348 int effectinfoindex;
1351 particleeffectinfo_t *info;
1358 qboolean underwater;
1359 qboolean immediatebloodstain;
1361 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1362 VectorLerp(originmins, 0.5, originmaxs, center);
1363 supercontents = CL_PointSuperContents(center);
1364 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1365 VectorSubtract(originmaxs, originmins, traildir);
1366 traillen = VectorLength(traildir);
1367 VectorNormalize(traildir);
1368 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1370 if (info->effectnameindex == effectnameindex)
1373 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1375 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1378 // spawn a dlight if requested
1379 if (info->lightradiusstart > 0 && spawndlight)
1381 matrix4x4_t tempmatrix;
1382 if (info->trailspacing > 0)
1383 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1385 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1386 if (info->lighttime > 0 && info->lightradiusfade > 0)
1388 // light flash (explosion, etc)
1389 // called when effect starts
1390 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);
1392 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1395 // called by CL_LinkNetworkEntity
1396 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1397 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);
1398 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1402 if (!spawnparticles)
1407 if (info->tex[1] > info->tex[0])
1409 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1410 tex = min(tex, info->tex[1] - 1);
1412 if(info->staintex[0] < 0)
1413 staintex = info->staintex[0];
1416 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1417 staintex = min(staintex, info->staintex[1] - 1);
1419 if (info->particletype == pt_decal)
1420 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]);
1421 else if (info->orientation == PARTICLE_HBEAM)
1422 CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], 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]), 0, 0);
1425 if (!cl_particles.integer)
1427 switch (info->particletype)
1429 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1430 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1431 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1432 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1433 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1434 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1437 VectorCopy(originmins, trailpos);
1438 if (info->trailspacing > 0)
1440 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1441 trailstep = info->trailspacing / cl_particles_quality.value;
1442 immediatebloodstain = false;
1446 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1448 immediatebloodstain = info->particletype == pt_blood || staintex;
1450 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1451 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1453 if (info->tex[1] > info->tex[0])
1455 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1456 tex = min(tex, info->tex[1] - 1);
1460 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1461 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1462 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1465 part = CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], 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]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]));
1466 if (immediatebloodstain && part)
1468 immediatebloodstain = false;
1469 CL_ImmediateBloodStain(part);
1472 VectorMA(trailpos, trailstep, traildir, trailpos);
1479 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1482 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)
1484 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1492 void CL_EntityParticles (const entity_t *ent)
1495 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1496 static vec3_t avelocities[NUMVERTEXNORMALS];
1497 if (!cl_particles.integer) return;
1498 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1500 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1502 if (!avelocities[0][0])
1503 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1504 avelocities[0][i] = lhrandom(0, 2.55);
1506 for (i = 0;i < NUMVERTEXNORMALS;i++)
1508 yaw = cl.time * avelocities[i][0];
1509 pitch = cl.time * avelocities[i][1];
1510 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1511 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1512 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1513 CL_NewParticle(org, pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1518 void CL_ReadPointFile_f (void)
1520 vec3_t org, leakorg;
1522 char *pointfile = NULL, *pointfilepos, *t, tchar;
1523 char name[MAX_OSPATH];
1528 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1529 strlcat (name, ".pts", sizeof (name));
1530 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1533 Con_Printf("Could not open %s\n", name);
1537 Con_Printf("Reading %s...\n", name);
1538 VectorClear(leakorg);
1541 pointfilepos = pointfile;
1542 while (*pointfilepos)
1544 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1549 while (*t && *t != '\n' && *t != '\r')
1553 #if _MSC_VER >= 1400
1554 #define sscanf sscanf_s
1556 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1562 VectorCopy(org, leakorg);
1565 if (cl.num_particles < cl.max_particles - 3)
1568 CL_NewParticle(org, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1571 Mem_Free(pointfile);
1572 VectorCopy(leakorg, org);
1573 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1575 CL_NewParticle(org, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0);
1576 CL_NewParticle(org, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0);
1577 CL_NewParticle(org, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0);
1582 CL_ParseParticleEffect
1584 Parse an effect out of the server message
1587 void CL_ParseParticleEffect (void)
1590 int i, count, msgcount, color;
1592 MSG_ReadVector(org, cls.protocol);
1593 for (i=0 ; i<3 ; i++)
1594 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1595 msgcount = MSG_ReadByte ();
1596 color = MSG_ReadByte ();
1598 if (msgcount == 255)
1603 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1608 CL_ParticleExplosion
1612 void CL_ParticleExplosion (const vec3_t org)
1618 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1619 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1621 if (cl_particles_quake.integer)
1623 for (i = 0;i < 1024;i++)
1629 color = particlepalette[ramp1[r]];
1630 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1634 color = particlepalette[ramp2[r]];
1635 CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1641 i = CL_PointSuperContents(org);
1642 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1644 if (cl_particles.integer && cl_particles_bubbles.integer)
1645 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1646 CL_NewParticle(org, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1650 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1652 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1656 for (k = 0;k < 16;k++)
1659 VectorMA(org, 128, v2, v);
1660 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1661 if (trace.fraction >= 0.1)
1664 VectorSubtract(trace.endpos, org, v2);
1665 VectorScale(v2, 2.0f, v2);
1666 CL_NewParticle(org, pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1672 if (cl_particles_explosions_shell.integer)
1673 R_NewExplosion(org);
1678 CL_ParticleExplosion2
1682 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1685 if (!cl_particles.integer) return;
1687 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1689 k = particlepalette[colorStart + (i % colorLength)];
1690 if (cl_particles_quake.integer)
1691 CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1693 CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1697 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1700 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1701 if (cl_particles_sparks.integer)
1703 sparkcount *= cl_particles_quality.value;
1704 while(sparkcount-- > 0)
1705 CL_NewParticle(center, pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1709 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1712 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1713 if (cl_particles_smoke.integer)
1715 smokecount *= cl_particles_quality.value;
1716 while(smokecount-- > 0)
1717 CL_NewParticle(center, pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1721 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)
1725 if (!cl_particles.integer) return;
1726 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1728 count = (int)(count * cl_particles_quality.value);
1731 k = particlepalette[colorbase + (rand()&3)];
1732 CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1736 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1739 float minz, maxz, lifetime = 30;
1741 if (!cl_particles.integer) return;
1742 if (dir[2] < 0) // falling
1744 minz = maxs[2] + dir[2] * 0.1;
1747 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1752 maxz = maxs[2] + dir[2] * 0.1;
1754 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1757 count = (int)(count * cl_particles_quality.value);
1762 if (!cl_particles_rain.integer) break;
1763 count *= 4; // ick, this should be in the mod or maps?
1767 k = particlepalette[colorbase + (rand()&3)];
1768 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1769 if (gamemode == GAME_GOODVSBAD2)
1770 CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1772 CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0);
1776 if (!cl_particles_snow.integer) break;
1779 k = particlepalette[colorbase + (rand()&3)];
1780 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1781 if (gamemode == GAME_GOODVSBAD2)
1782 CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1784 CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0);
1788 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1792 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1793 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1794 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1795 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1797 #define PARTICLETEXTURESIZE 64
1798 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1800 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1804 dz = 1 - (dx*dx+dy*dy);
1805 if (dz > 0) // it does hit the sphere
1809 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1810 VectorNormalize(normal);
1811 dot = DotProduct(normal, light);
1812 if (dot > 0.5) // interior reflection
1813 f += ((dot * 2) - 1);
1814 else if (dot < -0.5) // exterior reflection
1815 f += ((dot * -2) - 1);
1817 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1818 VectorNormalize(normal);
1819 dot = DotProduct(normal, light);
1820 if (dot > 0.5) // interior reflection
1821 f += ((dot * 2) - 1);
1822 else if (dot < -0.5) // exterior reflection
1823 f += ((dot * -2) - 1);
1825 f += 16; // just to give it a haze so you can see the outline
1826 f = bound(0, f, 255);
1827 return (unsigned char) f;
1833 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1834 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1836 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1837 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1838 *width = particlefontcellwidth;
1839 *height = particlefontcellheight;
1842 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1844 int basex, basey, w, h, y;
1845 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1846 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1847 Sys_Error("invalid particle texture size for autogenerating");
1848 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1849 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1852 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1855 float cx, cy, dx, dy, f, iradius;
1857 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1858 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1859 iradius = 1.0f / radius;
1860 alpha *= (1.0f / 255.0f);
1861 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1863 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1867 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1872 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1873 d[0] += (int)(f * (blue - d[0]));
1874 d[1] += (int)(f * (green - d[1]));
1875 d[2] += (int)(f * (red - d[2]));
1881 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1884 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1886 data[0] = bound(minb, data[0], maxb);
1887 data[1] = bound(ming, data[1], maxg);
1888 data[2] = bound(minr, data[2], maxr);
1892 void particletextureinvert(unsigned char *data)
1895 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1897 data[0] = 255 - data[0];
1898 data[1] = 255 - data[1];
1899 data[2] = 255 - data[2];
1903 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1904 static void R_InitBloodTextures (unsigned char *particletexturedata)
1907 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1908 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1911 for (i = 0;i < 8;i++)
1913 memset(data, 255, datasize);
1914 for (k = 0;k < 24;k++)
1915 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1916 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1917 particletextureinvert(data);
1918 setuptex(tex_bloodparticle[i], data, particletexturedata);
1922 for (i = 0;i < 8;i++)
1924 memset(data, 255, datasize);
1926 for (j = 1;j < 10;j++)
1927 for (k = min(j, m - 1);k < m;k++)
1928 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1929 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1930 particletextureinvert(data);
1931 setuptex(tex_blooddecal[i], data, particletexturedata);
1937 //uncomment this to make engine save out particle font to a tga file when run
1938 //#define DUMPPARTICLEFONT
1940 static void R_InitParticleTexture (void)
1942 int x, y, d, i, k, m;
1943 int basex, basey, w, h;
1944 float dx, dy, f, s1, t1, s2, t2;
1947 fs_offset_t filesize;
1948 char texturename[MAX_QPATH];
1950 // a note: decals need to modulate (multiply) the background color to
1951 // properly darken it (stain), and they need to be able to alpha fade,
1952 // this is a very difficult challenge because it means fading to white
1953 // (no change to background) rather than black (darkening everything
1954 // behind the whole decal polygon), and to accomplish this the texture is
1955 // inverted (dark red blood on white background becomes brilliant cyan
1956 // and white on black background) so we can alpha fade it to black, then
1957 // we invert it again during the blendfunc to make it work...
1959 #ifndef DUMPPARTICLEFONT
1960 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1963 particlefonttexture = decalskinframe->base;
1964 // TODO maybe allow custom grid size?
1965 particlefontwidth = image_width;
1966 particlefontheight = image_height;
1967 particlefontcellwidth = image_width / 8;
1968 particlefontcellheight = image_height / 8;
1969 particlefontcols = 8;
1970 particlefontrows = 8;
1975 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1976 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1977 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1978 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1979 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1981 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1982 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1983 particlefontcols = 8;
1984 particlefontrows = 8;
1986 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1989 for (i = 0;i < 8;i++)
1991 memset(data, 255, datasize);
1994 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1995 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1997 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1999 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2000 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2002 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2003 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2005 d = (int)(d * (1-(dx*dx+dy*dy)));
2006 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2007 d = bound(0, d, 255);
2008 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2015 setuptex(tex_smoke[i], data, particletexturedata);
2019 memset(data, 255, datasize);
2020 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2022 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2023 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2025 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2026 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2027 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2030 setuptex(tex_rainsplash, data, particletexturedata);
2033 memset(data, 255, datasize);
2034 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2036 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2037 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2039 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2040 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2041 d = bound(0, d, 255);
2042 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2045 setuptex(tex_particle, data, particletexturedata);
2048 memset(data, 255, datasize);
2049 light[0] = 1;light[1] = 1;light[2] = 1;
2050 VectorNormalize(light);
2051 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2053 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2054 // stretch upper half of bubble by +50% and shrink lower half by -50%
2055 // (this gives an elongated teardrop shape)
2057 dy = (dy - 0.5f) * 2.0f;
2059 dy = (dy - 0.5f) / 1.5f;
2060 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2062 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2063 // shrink bubble width to half
2065 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2068 setuptex(tex_raindrop, data, particletexturedata);
2071 memset(data, 255, datasize);
2072 light[0] = 1;light[1] = 1;light[2] = 1;
2073 VectorNormalize(light);
2074 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2076 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2077 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2079 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2080 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2083 setuptex(tex_bubble, data, particletexturedata);
2085 // Blood particles and blood decals
2086 R_InitBloodTextures (particletexturedata);
2089 for (i = 0;i < 8;i++)
2091 memset(data, 255, datasize);
2092 for (k = 0;k < 12;k++)
2093 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2094 for (k = 0;k < 3;k++)
2095 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2096 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2097 particletextureinvert(data);
2098 setuptex(tex_bulletdecal[i], data, particletexturedata);
2101 #ifdef DUMPPARTICLEFONT
2102 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2105 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2106 particlefonttexture = decalskinframe->base;
2108 Mem_Free(particletexturedata);
2113 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2115 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2116 particletexture[i].texture = particlefonttexture;
2117 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2118 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2119 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2120 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2123 #ifndef DUMPPARTICLEFONT
2124 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2125 if (!particletexture[tex_beam].texture)
2128 unsigned char noise3[64][64], data2[64][16][4];
2130 fractalnoise(&noise3[0][0], 64, 4);
2132 for (y = 0;y < 64;y++)
2134 dy = (y - 0.5f*64) / (64*0.5f-1);
2135 for (x = 0;x < 16;x++)
2137 dx = (x - 0.5f*16) / (16*0.5f-2);
2138 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2139 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2140 data2[y][x][3] = 255;
2144 #ifdef DUMPPARTICLEFONT
2145 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2147 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2149 particletexture[tex_beam].s1 = 0;
2150 particletexture[tex_beam].t1 = 0;
2151 particletexture[tex_beam].s2 = 1;
2152 particletexture[tex_beam].t2 = 1;
2154 // now load an texcoord/texture override file
2155 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2162 if(!COM_ParseToken_Simple(&bufptr, true, false))
2164 if(!strcmp(com_token, "\n"))
2165 continue; // empty line
2166 i = atoi(com_token);
2174 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2176 s1 = atof(com_token);
2177 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2179 t1 = atof(com_token);
2180 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2182 s2 = atof(com_token);
2183 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2185 t2 = atof(com_token);
2186 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2187 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2188 strlcpy(texturename, com_token, sizeof(texturename));
2195 strlcpy(texturename, com_token, sizeof(texturename));
2198 if (!texturename[0])
2200 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2203 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2205 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2208 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2209 particletexture[i].s1 = s1;
2210 particletexture[i].t1 = t1;
2211 particletexture[i].s2 = s2;
2212 particletexture[i].t2 = t2;
2218 static void r_part_start(void)
2221 // generate particlepalette for convenience from the main one
2222 for (i = 0;i < 256;i++)
2223 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2224 particletexturepool = R_AllocTexturePool();
2225 R_InitParticleTexture ();
2226 CL_Particles_LoadEffectInfo();
2229 static void r_part_shutdown(void)
2231 R_FreeTexturePool(&particletexturepool);
2234 static void r_part_newmap(void)
2237 R_SkinFrame_MarkUsed(decalskinframe);
2238 CL_Particles_LoadEffectInfo();
2241 #define BATCHSIZE 256
2242 unsigned short particle_elements[BATCHSIZE*6];
2243 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2245 void R_Particles_Init (void)
2248 for (i = 0;i < BATCHSIZE;i++)
2250 particle_elements[i*6+0] = i*4+0;
2251 particle_elements[i*6+1] = i*4+1;
2252 particle_elements[i*6+2] = i*4+2;
2253 particle_elements[i*6+3] = i*4+0;
2254 particle_elements[i*6+4] = i*4+2;
2255 particle_elements[i*6+5] = i*4+3;
2258 Cvar_RegisterVariable(&r_drawparticles);
2259 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2260 Cvar_RegisterVariable(&r_drawdecals);
2261 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2262 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2265 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2267 int surfacelistindex;
2269 float *v3f, *t2f, *c4f;
2270 particletexture_t *tex;
2271 float right[3], up[3], size, ca;
2272 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2274 RSurf_ActiveWorldEntity();
2276 r_refdef.stats.drawndecals += numsurfaces;
2277 R_Mesh_ResetTextureState();
2278 GL_DepthMask(false);
2279 GL_DepthRange(0, 1);
2280 GL_PolygonOffset(0, 0);
2282 GL_CullFace(GL_NONE);
2284 // generate all the vertices at once
2285 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2287 d = cl.decals + surfacelist[surfacelistindex];
2290 c4f = particle_color4f + 16*surfacelistindex;
2291 ca = d->alpha * alphascale;
2292 // ensure alpha multiplier saturates properly
2293 if (ca > 1.0f / 256.0f)
2295 if (r_refdef.fogenabled)
2296 ca *= RSurf_FogVertex(d->org);
2297 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2298 Vector4Copy(c4f, c4f + 4);
2299 Vector4Copy(c4f, c4f + 8);
2300 Vector4Copy(c4f, c4f + 12);
2302 // calculate vertex positions
2303 size = d->size * cl_particles_size.value;
2304 VectorVectors(d->normal, right, up);
2305 VectorScale(right, size, right);
2306 VectorScale(up, size, up);
2307 v3f = particle_vertex3f + 12*surfacelistindex;
2308 v3f[ 0] = d->org[0] - right[0] - up[0];
2309 v3f[ 1] = d->org[1] - right[1] - up[1];
2310 v3f[ 2] = d->org[2] - right[2] - up[2];
2311 v3f[ 3] = d->org[0] - right[0] + up[0];
2312 v3f[ 4] = d->org[1] - right[1] + up[1];
2313 v3f[ 5] = d->org[2] - right[2] + up[2];
2314 v3f[ 6] = d->org[0] + right[0] + up[0];
2315 v3f[ 7] = d->org[1] + right[1] + up[1];
2316 v3f[ 8] = d->org[2] + right[2] + up[2];
2317 v3f[ 9] = d->org[0] + right[0] - up[0];
2318 v3f[10] = d->org[1] + right[1] - up[1];
2319 v3f[11] = d->org[2] + right[2] - up[2];
2321 // calculate texcoords
2322 tex = &particletexture[d->texnum];
2323 t2f = particle_texcoord2f + 8*surfacelistindex;
2324 t2f[0] = tex->s1;t2f[1] = tex->t2;
2325 t2f[2] = tex->s1;t2f[3] = tex->t1;
2326 t2f[4] = tex->s2;t2f[5] = tex->t1;
2327 t2f[6] = tex->s2;t2f[7] = tex->t2;
2330 // now render the decals all at once
2331 // (this assumes they all use one particle font texture!)
2332 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2333 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2334 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2335 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2338 void R_DrawDecals (void)
2341 int drawdecals = r_drawdecals.integer;
2346 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2348 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2349 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2351 // LordHavoc: early out conditions
2355 decalfade = frametime * 256 / cl_decals_fadetime.value;
2356 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2357 drawdist2 = drawdist2*drawdist2;
2359 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2361 if (!decal->typeindex)
2364 if (killsequence - decal->decalsequence > 0)
2367 if (cl.time > decal->time2 + cl_decals_time.value)
2369 decal->alpha -= decalfade;
2370 if (decal->alpha <= 0)
2376 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2378 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2379 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2385 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2391 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))
2392 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2395 decal->typeindex = 0;
2396 if (cl.free_decal > i)
2400 // reduce cl.num_decals if possible
2401 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2404 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2406 decal_t *olddecals = cl.decals;
2407 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2408 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2409 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2410 Mem_Free(olddecals);
2413 r_refdef.stats.totaldecals = cl.num_decals;
2416 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2418 int surfacelistindex;
2419 int batchstart, batchcount;
2420 const particle_t *p;
2422 rtexture_t *texture;
2423 float *v3f, *t2f, *c4f;
2424 particletexture_t *tex;
2425 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2426 float ambient[3], diffuse[3], diffusenormal[3];
2427 float spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2428 vec4_t colormultiplier;
2430 RSurf_ActiveWorldEntity();
2432 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));
2434 r_refdef.stats.particles += numsurfaces;
2435 R_Mesh_ResetTextureState();
2436 GL_DepthMask(false);
2437 GL_DepthRange(0, 1);
2438 GL_PolygonOffset(0, 0);
2440 GL_AlphaTest(false);
2441 GL_CullFace(GL_NONE);
2443 spintime = r_refdef.scene.time;
2445 // first generate all the vertices at once
2446 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2448 p = cl.particles + surfacelist[surfacelistindex];
2450 blendmode = (pblend_t)p->blendmode;
2454 case PBLEND_INVALID:
2456 alpha = p->alpha * colormultiplier[3];
2457 // ensure alpha multiplier saturates properly
2460 // additive and modulate can just fade out in fog (this is correct)
2461 if (r_refdef.fogenabled)
2462 alpha *= RSurf_FogVertex(p->org);
2463 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2464 alpha *= 1.0f / 256.0f;
2465 c4f[0] = p->color[0] * alpha;
2466 c4f[1] = p->color[1] * alpha;
2467 c4f[2] = p->color[2] * alpha;
2471 alpha = p->alpha * colormultiplier[3];
2472 // ensure alpha multiplier saturates properly
2475 // additive and modulate can just fade out in fog (this is correct)
2476 if (r_refdef.fogenabled)
2477 alpha *= RSurf_FogVertex(p->org);
2478 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2479 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2480 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2481 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2485 c4f[0] = p->color[0] * colormultiplier[0];
2486 c4f[1] = p->color[1] * colormultiplier[1];
2487 c4f[2] = p->color[2] * colormultiplier[2];
2488 c4f[3] = p->alpha * colormultiplier[3];
2489 // note: lighting is not cheap!
2490 if (particletype[p->typeindex].lighting)
2492 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2493 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2494 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2495 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2497 // mix in the fog color
2498 if (r_refdef.fogenabled)
2500 fog = RSurf_FogVertex(p->org);
2502 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2503 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2504 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2508 // copy the color into the other three vertices
2509 Vector4Copy(c4f, c4f + 4);
2510 Vector4Copy(c4f, c4f + 8);
2511 Vector4Copy(c4f, c4f + 12);
2513 size = p->size * cl_particles_size.value;
2514 tex = &particletexture[p->texnum];
2515 switch(p->orientation)
2517 // case PARTICLE_INVALID:
2518 case PARTICLE_BILLBOARD:
2519 if (p->angle + p->spin)
2521 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2522 spinsin = sin(spinrad) * size;
2523 spincos = cos(spinrad) * size;
2524 spinm1 = -p->stretch * spincos;
2527 spinm4 = -p->stretch * spincos;
2528 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2529 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2533 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2534 VectorScale(r_refdef.view.up, size, up);
2537 v3f[ 0] = p->org[0] - right[0] - up[0];
2538 v3f[ 1] = p->org[1] - right[1] - up[1];
2539 v3f[ 2] = p->org[2] - right[2] - up[2];
2540 v3f[ 3] = p->org[0] - right[0] + up[0];
2541 v3f[ 4] = p->org[1] - right[1] + up[1];
2542 v3f[ 5] = p->org[2] - right[2] + up[2];
2543 v3f[ 6] = p->org[0] + right[0] + up[0];
2544 v3f[ 7] = p->org[1] + right[1] + up[1];
2545 v3f[ 8] = p->org[2] + right[2] + up[2];
2546 v3f[ 9] = p->org[0] + right[0] - up[0];
2547 v3f[10] = p->org[1] + right[1] - up[1];
2548 v3f[11] = p->org[2] + right[2] - up[2];
2549 t2f[0] = tex->s1;t2f[1] = tex->t2;
2550 t2f[2] = tex->s1;t2f[3] = tex->t1;
2551 t2f[4] = tex->s2;t2f[5] = tex->t1;
2552 t2f[6] = tex->s2;t2f[7] = tex->t2;
2554 case PARTICLE_ORIENTED_DOUBLESIDED:
2555 VectorVectors(p->vel, baseright, baseup);
2556 if (p->angle + p->spin)
2558 spinrad = (p->angle + p->spin * spintime) * (float)(M_PI / 180.0f);
2559 spinsin = sin(spinrad) * size;
2560 spincos = cos(spinrad) * size;
2561 spinm1 = p->stretch * spincos;
2564 spinm4 = p->stretch * spincos;
2565 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2566 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2570 VectorScale(baseright, size * p->stretch, right);
2571 VectorScale(baseup, size, up);
2573 v3f[ 0] = p->org[0] - right[0] - up[0];
2574 v3f[ 1] = p->org[1] - right[1] - up[1];
2575 v3f[ 2] = p->org[2] - right[2] - up[2];
2576 v3f[ 3] = p->org[0] - right[0] + up[0];
2577 v3f[ 4] = p->org[1] - right[1] + up[1];
2578 v3f[ 5] = p->org[2] - right[2] + up[2];
2579 v3f[ 6] = p->org[0] + right[0] + up[0];
2580 v3f[ 7] = p->org[1] + right[1] + up[1];
2581 v3f[ 8] = p->org[2] + right[2] + up[2];
2582 v3f[ 9] = p->org[0] + right[0] - up[0];
2583 v3f[10] = p->org[1] + right[1] - up[1];
2584 v3f[11] = p->org[2] + right[2] - up[2];
2585 t2f[0] = tex->s1;t2f[1] = tex->t2;
2586 t2f[2] = tex->s1;t2f[3] = tex->t1;
2587 t2f[4] = tex->s2;t2f[5] = tex->t1;
2588 t2f[6] = tex->s2;t2f[7] = tex->t2;
2590 case PARTICLE_SPARK:
2591 len = VectorLength(p->vel);
2592 VectorNormalize2(p->vel, up);
2593 lenfactor = p->stretch * 0.04 * len;
2594 if(lenfactor < size * 0.5)
2595 lenfactor = size * 0.5;
2596 VectorMA(p->org, -lenfactor, up, v);
2597 VectorMA(p->org, lenfactor, up, up2);
2598 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2599 t2f[0] = tex->s1;t2f[1] = tex->t2;
2600 t2f[2] = tex->s1;t2f[3] = tex->t1;
2601 t2f[4] = tex->s2;t2f[5] = tex->t1;
2602 t2f[6] = tex->s2;t2f[7] = tex->t2;
2604 case PARTICLE_VBEAM:
2605 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2606 VectorSubtract(p->vel, p->org, up);
2607 VectorNormalize(up);
2608 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2609 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2610 t2f[0] = tex->s2;t2f[1] = v[0];
2611 t2f[2] = tex->s1;t2f[3] = v[0];
2612 t2f[4] = tex->s1;t2f[5] = v[1];
2613 t2f[6] = tex->s2;t2f[7] = v[1];
2615 case PARTICLE_HBEAM:
2616 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2617 VectorSubtract(p->vel, p->org, up);
2618 VectorNormalize(up);
2619 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2620 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2621 t2f[0] = v[0];t2f[1] = tex->t1;
2622 t2f[2] = v[0];t2f[3] = tex->t2;
2623 t2f[4] = v[1];t2f[5] = tex->t2;
2624 t2f[6] = v[1];t2f[7] = tex->t1;
2629 // now render batches of particles based on blendmode and texture
2630 blendmode = PBLEND_INVALID;
2634 R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f);
2635 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2637 p = cl.particles + surfacelist[surfacelistindex];
2639 if (blendmode != p->blendmode)
2641 blendmode = (pblend_t)p->blendmode;
2645 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2647 case PBLEND_INVALID:
2649 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2652 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2656 if (texture != particletexture[p->texnum].texture)
2658 texture = particletexture[p->texnum].texture;
2659 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2662 // iterate until we find a change in settings
2663 batchstart = surfacelistindex++;
2664 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2666 p = cl.particles + surfacelist[surfacelistindex];
2667 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2671 batchcount = surfacelistindex - batchstart;
2672 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0);
2676 void R_DrawParticles (void)
2679 int drawparticles = r_drawparticles.integer;
2680 float minparticledist;
2682 float gravity, frametime, f, dist, oldorg[3];
2688 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2689 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2691 // LordHavoc: early out conditions
2692 if (!cl.num_particles)
2695 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2696 gravity = frametime * cl.movevars_gravity;
2697 update = frametime > 0;
2698 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2699 drawdist2 = drawdist2*drawdist2;
2701 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2705 if (cl.free_particle > i)
2706 cl.free_particle = i;
2712 if (p->delayedspawn > cl.time)
2714 p->delayedspawn = 0;
2716 p->size += p->sizeincrease * frametime;
2717 p->alpha -= p->alphafade * frametime;
2719 if (p->alpha <= 0 || p->die <= cl.time)
2722 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2724 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2726 if (p->typeindex == pt_blood)
2727 p->size += frametime * 8;
2729 p->vel[2] -= p->gravity * gravity;
2730 f = 1.0f - min(p->liquidfriction * frametime, 1);
2731 VectorScale(p->vel, f, p->vel);
2735 p->vel[2] -= p->gravity * gravity;
2738 f = 1.0f - min(p->airfriction * frametime, 1);
2739 VectorScale(p->vel, f, p->vel);
2743 VectorCopy(p->org, oldorg);
2744 VectorMA(p->org, frametime, p->vel, p->org);
2745 // if (p->bounce && cl.time >= p->delayedcollisions)
2746 if (p->bounce && cl_particles_collisions.integer)
2748 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);
2749 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2750 // or if the trace hit something flagged as NOIMPACT
2751 // then remove the particle
2752 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2754 VectorCopy(trace.endpos, p->org);
2755 // react if the particle hit something
2756 if (trace.fraction < 1)
2758 VectorCopy(trace.endpos, p->org);
2760 if (p->staintexnum >= 0)
2762 // blood - splash on solid
2763 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2766 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2767 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2768 if (cl_decals.integer)
2770 // create a decal for the blood splat
2771 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2772 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2777 if (p->typeindex == pt_blood)
2779 // blood - splash on solid
2780 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2782 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2784 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)));
2785 if (cl_decals.integer)
2787 // create a decal for the blood splat
2788 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);
2793 else if (p->bounce < 0)
2795 // bounce -1 means remove on impact
2800 // anything else - bounce off solid
2801 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2802 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2803 if (DotProduct(p->vel, p->vel) < 0.03)
2804 VectorClear(p->vel);
2810 if (p->typeindex != pt_static)
2812 switch (p->typeindex)
2814 case pt_entityparticle:
2815 // particle that removes itself after one rendered frame
2822 a = CL_PointSuperContents(p->org);
2823 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2827 a = CL_PointSuperContents(p->org);
2828 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2832 a = CL_PointSuperContents(p->org);
2833 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2837 if (cl.time > p->time2)
2840 p->time2 = cl.time + (rand() & 3) * 0.1;
2841 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2842 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2844 a = CL_PointSuperContents(p->org);
2845 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2853 else if (p->delayedspawn)
2857 // don't render particles too close to the view (they chew fillrate)
2858 // also don't render particles behind the view (useless)
2859 // further checks to cull to the frustum would be too slow here
2860 switch(p->typeindex)
2863 // beams have no culling
2864 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2867 if(cl_particles_visculling.integer)
2868 if (!r_refdef.viewcache.world_novis)
2869 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2871 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2873 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2876 // anything else just has to be in front of the viewer and visible at this distance
2877 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2878 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2885 if (cl.free_particle > i)
2886 cl.free_particle = i;
2889 // reduce cl.num_particles if possible
2890 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2893 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2895 particle_t *oldparticles = cl.particles;
2896 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2897 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2898 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2899 Mem_Free(oldparticles);