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];
126 int numparticleeffectinfo;
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_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"};
225 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
226 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
227 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
230 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
235 particleeffectinfo_t *info = NULL;
236 const char *text = textstart;
238 for (linenumber = 1;;linenumber++)
241 for (arrayindex = 0;arrayindex < 16;arrayindex++)
242 argv[arrayindex][0] = 0;
245 if (!COM_ParseToken_Simple(&text, true, false))
247 if (!strcmp(com_token, "\n"))
251 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
257 #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;}
258 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
259 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
260 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
261 #define readfloat(var) checkparms(2);var = atof(argv[1])
262 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
263 if (!strcmp(argv[0], "effect"))
267 if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO)
269 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
272 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
274 if (particleeffectname[effectnameindex][0])
276 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
281 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
285 // if we run out of names, abort
286 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
288 Con_Printf("%s:%i: too many effects!\n", filename, linenumber);
291 info = particleeffectinfo + numparticleeffectinfo++;
292 info->effectnameindex = effectnameindex;
293 info->particletype = pt_alphastatic;
294 info->blendmode = particletype[info->particletype].blendmode;
295 info->orientation = particletype[info->particletype].orientation;
296 info->tex[0] = tex_particle;
297 info->tex[1] = tex_particle;
298 info->color[0] = 0xFFFFFF;
299 info->color[1] = 0xFFFFFF;
303 info->alpha[1] = 256;
304 info->alpha[2] = 256;
305 info->time[0] = 9999;
306 info->time[1] = 9999;
307 VectorSet(info->lightcolor, 1, 1, 1);
308 info->lightshadow = true;
309 info->lighttime = 9999;
310 info->stretchfactor = 1;
311 info->staincolor[0] = (unsigned int)-1;
312 info->staincolor[1] = (unsigned int)-1;
313 info->staintex[0] = -1;
314 info->staintex[1] = -1;
315 info->stainalpha[0] = 1;
316 info->stainalpha[1] = 1;
317 info->stainsize[0] = 2;
318 info->stainsize[1] = 2;
320 info->rotate[1] = 360;
324 else if (info == NULL)
326 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
329 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
330 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
331 else if (!strcmp(argv[0], "type"))
334 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
335 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
336 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
337 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
338 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
339 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
340 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
341 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
342 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
343 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
344 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
345 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
346 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
347 info->blendmode = particletype[info->particletype].blendmode;
348 info->orientation = particletype[info->particletype].orientation;
350 else if (!strcmp(argv[0], "blend"))
353 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
354 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
355 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
356 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
358 else if (!strcmp(argv[0], "orientation"))
361 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
362 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
363 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
364 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
365 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
367 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
368 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
369 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
370 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
371 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
372 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
373 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
374 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
375 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
376 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
377 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
378 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
379 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
380 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
381 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
382 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
383 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
384 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
385 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
386 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
387 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
388 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
389 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
390 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
391 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
392 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
393 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
394 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
395 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
396 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; }
397 else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);}
399 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
408 int CL_ParticleEffectIndexForName(const char *name)
411 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
412 if (!strcmp(particleeffectname[i], name))
417 const char *CL_ParticleEffectNameForIndex(int i)
419 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
421 return particleeffectname[i];
424 // MUST match effectnameindex_t in client.h
425 static const char *standardeffectnames[EFFECT_TOTAL] =
449 "TE_TEI_BIGEXPLOSION",
465 void CL_Particles_LoadEffectInfo(void)
469 unsigned char *filedata;
470 fs_offset_t filesize;
471 char filename[MAX_QPATH];
472 numparticleeffectinfo = 0;
473 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
474 memset(particleeffectname, 0, sizeof(particleeffectname));
475 for (i = 0;i < EFFECT_TOTAL;i++)
476 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
477 for (filepass = 0;;filepass++)
480 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
481 else if (filepass == 1)
483 if (!cl.worldbasename[0])
485 dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension);
489 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
492 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
502 void CL_ReadPointFile_f (void);
503 void CL_Particles_Init (void)
505 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)");
506 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
508 Cvar_RegisterVariable (&cl_particles);
509 Cvar_RegisterVariable (&cl_particles_quality);
510 Cvar_RegisterVariable (&cl_particles_alpha);
511 Cvar_RegisterVariable (&cl_particles_size);
512 Cvar_RegisterVariable (&cl_particles_quake);
513 Cvar_RegisterVariable (&cl_particles_blood);
514 Cvar_RegisterVariable (&cl_particles_blood_alpha);
515 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
516 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
517 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
518 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
519 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
520 Cvar_RegisterVariable (&cl_particles_explosions_shell);
521 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
522 Cvar_RegisterVariable (&cl_particles_rain);
523 Cvar_RegisterVariable (&cl_particles_snow);
524 Cvar_RegisterVariable (&cl_particles_smoke);
525 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
526 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
527 Cvar_RegisterVariable (&cl_particles_sparks);
528 Cvar_RegisterVariable (&cl_particles_bubbles);
529 Cvar_RegisterVariable (&cl_particles_visculling);
530 Cvar_RegisterVariable (&cl_particles_collisions);
531 Cvar_RegisterVariable (&cl_decals);
532 Cvar_RegisterVariable (&cl_decals_visculling);
533 Cvar_RegisterVariable (&cl_decals_time);
534 Cvar_RegisterVariable (&cl_decals_fadetime);
535 Cvar_RegisterVariable (&cl_decals_newsystem);
536 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
537 Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain);
538 Cvar_RegisterVariable (&cl_decals_models);
539 Cvar_RegisterVariable (&cl_decals_bias);
540 Cvar_RegisterVariable (&cl_decals_max);
543 void CL_Particles_Shutdown (void)
547 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
548 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
550 // list of all 26 parameters:
551 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
552 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
553 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
554 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
555 // palpha - opacity of particle as 0-255 (can be more than 255)
556 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
557 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
558 // pgravity - how much effect gravity has on the particle (0-1)
559 // 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
560 // px,py,pz - starting origin of particle
561 // pvx,pvy,pvz - starting velocity of particle
562 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
563 // blendmode - one of the PBLEND_ values
564 // orientation - one of the PARTICLE_ values
565 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
566 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
567 // stainalpha: opacity of the stain as factor for alpha
568 // stainsize: size of the stain as factor for palpha
569 // angle: base rotation of the particle geometry around its center normal
570 // spin: rotation speed of the particle geometry around its center normal
571 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, float tint[4])
576 if (!cl_particles.integer)
578 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
579 if (cl.free_particle >= cl.max_particles)
582 lifetime = palpha / min(1, palphafade);
583 part = &cl.particles[cl.free_particle++];
584 if (cl.num_particles < cl.free_particle)
585 cl.num_particles = cl.free_particle;
586 memset(part, 0, sizeof(*part));
587 VectorCopy(sortorigin, part->sortorigin);
588 part->typeindex = ptypeindex;
589 part->blendmode = blendmode;
590 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
592 particletexture_t *tex = &particletexture[ptex];
593 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
594 part->orientation = PARTICLE_VBEAM;
596 part->orientation = PARTICLE_HBEAM;
599 part->orientation = orientation;
600 l2 = (int)lhrandom(0.5, 256.5);
602 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
603 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
604 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
605 part->alpha = palpha;
606 part->alphafade = palphafade;
607 part->staintexnum = staintex;
608 if(staincolor1 >= 0 && staincolor2 >= 0)
610 l2 = (int)lhrandom(0.5, 256.5);
612 if(blendmode == PBLEND_INVMOD)
614 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
615 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
616 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
620 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
621 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
622 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
624 if(r > 0xFF) r = 0xFF;
625 if(g > 0xFF) g = 0xFF;
626 if(b > 0xFF) b = 0xFF;
630 r = part->color[0]; // -1 is shorthand for stain = particle color
634 part->staincolor[0] = r;
635 part->staincolor[1] = g;
636 part->staincolor[2] = b;
637 part->stainalpha = palpha * stainalpha;
638 part->stainsize = psize * stainsize;
641 if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting
643 part->color[0] *= tint[0];
644 part->color[1] *= tint[1];
645 part->color[2] *= tint[2];
647 part->alpha *= tint[3];
648 part->alphafade *= tint[3];
649 part->stainalpha *= tint[3];
653 part->sizeincrease = psizeincrease;
654 part->gravity = pgravity;
655 part->bounce = pbounce;
656 part->stretch = stretch;
658 part->org[0] = px + originjitter * v[0];
659 part->org[1] = py + originjitter * v[1];
660 part->org[2] = pz + originjitter * v[2];
661 part->vel[0] = pvx + velocityjitter * v[0];
662 part->vel[1] = pvy + velocityjitter * v[1];
663 part->vel[2] = pvz + velocityjitter * v[2];
665 part->airfriction = pairfriction;
666 part->liquidfriction = pliquidfriction;
667 part->die = cl.time + lifetime;
668 part->delayedspawn = cl.time;
669 // part->delayedcollisions = 0;
670 part->qualityreduction = pqualityreduction;
673 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
674 if (part->typeindex == pt_rain)
678 float lifetime = part->die - cl.time;
681 // turn raindrop into simple spark and create delayedspawn splash effect
682 part->typeindex = pt_spark;
684 VectorMA(part->org, lifetime, part->vel, endvec);
685 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
686 part->die = cl.time + lifetime * trace.fraction;
687 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, NULL);
690 part2->delayedspawn = part->die;
691 part2->die += part->die - cl.time;
692 for (i = rand() & 7;i < 10;i++)
694 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, NULL);
697 part2->delayedspawn = part->die;
698 part2->die += part->die - cl.time;
704 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
706 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
709 VectorMA(part->org, lifetime, part->vel, endvec);
710 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
711 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
718 static void CL_ImmediateBloodStain(particle_t *part)
723 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
724 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
726 VectorCopy(part->vel, v);
728 staintex = part->staintexnum;
729 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);
732 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
733 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
735 VectorCopy(part->vel, v);
737 staintex = tex_blooddecal[rand()&7];
738 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);
742 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
746 entity_render_t *ent = &cl.entities[hitent].render;
747 unsigned char color[3];
748 if (!cl_decals.integer)
750 if (!ent->allowdecals)
753 l2 = (int)lhrandom(0.5, 256.5);
755 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
756 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
757 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
759 if (cl_decals_newsystem.integer)
761 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);
765 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
766 if (cl.free_decal >= cl.max_decals)
768 decal = &cl.decals[cl.free_decal++];
769 if (cl.num_decals < cl.free_decal)
770 cl.num_decals = cl.free_decal;
771 memset(decal, 0, sizeof(*decal));
772 decal->decalsequence = cl.decalsequence++;
773 decal->typeindex = pt_decal;
774 decal->texnum = texnum;
775 VectorMA(org, cl_decals_bias.value, normal, decal->org);
776 VectorCopy(normal, decal->normal);
778 decal->alpha = alpha;
779 decal->time2 = cl.time;
780 decal->color[0] = color[0];
781 decal->color[1] = color[1];
782 decal->color[2] = color[2];
783 decal->owner = hitent;
784 decal->clusterindex = -1000; // no vis culling unless we're sure
787 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
788 decal->ownermodel = cl.entities[decal->owner].render.model;
789 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
790 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
794 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
796 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
798 decal->clusterindex = leaf->clusterindex;
803 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
806 float bestfrac, bestorg[3], bestnormal[3];
808 int besthitent = 0, hitent;
811 for (i = 0;i < 32;i++)
814 VectorMA(org, maxdist, org2, org2);
815 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
816 // take the closest trace result that doesn't end up hitting a NOMARKS
817 // surface (sky for example)
818 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
820 bestfrac = trace.fraction;
822 VectorCopy(trace.endpos, bestorg);
823 VectorCopy(trace.plane.normal, bestnormal);
827 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
830 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
831 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
832 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)
835 matrix4x4_t tempmatrix;
837 VectorLerp(originmins, 0.5, originmaxs, center);
838 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
839 if (effectnameindex == EFFECT_SVC_PARTICLE)
841 if (cl_particles.integer)
843 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
845 CL_ParticleExplosion(center);
846 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
847 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
850 count *= cl_particles_quality.value;
851 for (;count > 0;count--)
853 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
854 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, NULL);
859 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
860 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
861 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
862 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
863 else if (effectnameindex == EFFECT_TE_SPIKE)
865 if (cl_particles_bulletimpacts.integer)
867 if (cl_particles_quake.integer)
869 if (cl_particles_smoke.integer)
870 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
874 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
875 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
876 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, NULL);
880 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
881 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
883 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
885 if (cl_particles_bulletimpacts.integer)
887 if (cl_particles_quake.integer)
889 if (cl_particles_smoke.integer)
890 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
894 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
895 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
896 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, NULL);
900 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
901 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
902 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);
904 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
906 if (cl_particles_bulletimpacts.integer)
908 if (cl_particles_quake.integer)
910 if (cl_particles_smoke.integer)
911 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
915 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
916 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
917 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, NULL);
921 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
922 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
924 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
926 if (cl_particles_bulletimpacts.integer)
928 if (cl_particles_quake.integer)
930 if (cl_particles_smoke.integer)
931 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
935 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
936 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
937 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, NULL);
941 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
942 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
943 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);
945 else if (effectnameindex == EFFECT_TE_BLOOD)
947 if (!cl_particles_blood.integer)
949 if (cl_particles_quake.integer)
950 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
953 static double bloodaccumulator = 0;
954 qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1);
955 //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, NULL);
956 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
957 for (;bloodaccumulator > 0;bloodaccumulator--)
959 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, NULL);
960 if (immediatebloodstain && part)
962 immediatebloodstain = false;
963 CL_ImmediateBloodStain(part);
968 else if (effectnameindex == EFFECT_TE_SPARK)
969 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
970 else if (effectnameindex == EFFECT_TE_PLASMABURN)
972 // plasma scorch mark
973 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
974 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
975 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
977 else if (effectnameindex == EFFECT_TE_GUNSHOT)
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, NULL);
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);
994 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
996 if (cl_particles_bulletimpacts.integer)
998 if (cl_particles_quake.integer)
999 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
1002 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
1003 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
1004 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, NULL);
1008 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
1009 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1010 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);
1012 else if (effectnameindex == EFFECT_TE_EXPLOSION)
1014 CL_ParticleExplosion(center);
1015 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);
1017 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
1019 CL_ParticleExplosion(center);
1020 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);
1022 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
1024 if (cl_particles_quake.integer)
1027 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1030 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, NULL);
1032 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, NULL);
1036 CL_ParticleExplosion(center);
1037 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);
1039 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1040 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);
1041 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1043 count *= cl_particles_quality.value;
1045 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, NULL);
1047 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1049 float i, j, inc, vel;
1052 inc = 8 / cl_particles_quality.value;
1053 for (i = -128;i < 128;i += inc)
1055 for (j = -128;j < 128;j += inc)
1057 dir[0] = j + lhrandom(0, inc);
1058 dir[1] = i + lhrandom(0, inc);
1060 org[0] = center[0] + dir[0];
1061 org[1] = center[1] + dir[1];
1062 org[2] = center[2] + lhrandom(0, 64);
1063 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1064 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, NULL);
1068 else if (effectnameindex == EFFECT_TE_TELEPORT)
1070 float i, j, k, inc, vel;
1073 if (cl_particles_quake.integer)
1074 inc = 4 / cl_particles_quality.value;
1076 inc = 8 / cl_particles_quality.value;
1077 for (i = -16;i < 16;i += inc)
1079 for (j = -16;j < 16;j += inc)
1081 for (k = -24;k < 32;k += inc)
1083 VectorSet(dir, i*8, j*8, k*8);
1084 VectorNormalize(dir);
1085 vel = lhrandom(50, 113);
1086 if (cl_particles_quake.integer)
1087 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, NULL);
1089 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, NULL);
1093 if (!cl_particles_quake.integer)
1094 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, NULL);
1095 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);
1097 else if (effectnameindex == EFFECT_TE_TEI_G3)
1098 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, NULL);
1099 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1101 if (cl_particles_smoke.integer)
1103 count *= 0.25f * cl_particles_quality.value;
1105 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, NULL);
1108 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1110 CL_ParticleExplosion(center);
1111 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);
1113 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1116 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1117 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1118 if (cl_particles_smoke.integer)
1119 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1120 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, NULL);
1121 if (cl_particles_sparks.integer)
1122 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1123 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, NULL);
1124 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);
1126 else if (effectnameindex == EFFECT_EF_FLAME)
1128 count *= 300 * cl_particles_quality.value;
1130 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, NULL);
1131 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);
1133 else if (effectnameindex == EFFECT_EF_STARDUST)
1135 count *= 200 * cl_particles_quality.value;
1137 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, NULL);
1138 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);
1140 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1144 int smoke, blood, bubbles, r, color;
1146 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1149 Vector4Set(light, 0, 0, 0, 0);
1151 if (effectnameindex == EFFECT_TR_ROCKET)
1152 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1153 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1155 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1156 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1158 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1160 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1161 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1165 matrix4x4_t tempmatrix;
1166 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1167 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);
1168 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1172 if (!spawnparticles)
1175 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1178 VectorSubtract(originmaxs, originmins, dir);
1179 len = VectorNormalizeLength(dir);
1182 dec = -ent->persistent.trail_time;
1183 ent->persistent.trail_time += len;
1184 if (ent->persistent.trail_time < 0.01f)
1187 // if we skip out, leave it reset
1188 ent->persistent.trail_time = 0.0f;
1193 // advance into this frame to reach the first puff location
1194 VectorMA(originmins, dec, dir, pos);
1197 smoke = cl_particles.integer && cl_particles_smoke.integer;
1198 blood = cl_particles.integer && cl_particles_blood.integer;
1199 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1200 qd = 1.0f / cl_particles_quality.value;
1207 if (effectnameindex == EFFECT_TR_BLOOD)
1209 if (cl_particles_quake.integer)
1211 color = particlepalette[67 + (rand()&3)];
1212 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, NULL);
1217 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, NULL);
1220 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1222 if (cl_particles_quake.integer)
1225 color = particlepalette[67 + (rand()&3)];
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, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL);
1231 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, NULL);
1237 if (effectnameindex == EFFECT_TR_ROCKET)
1239 if (cl_particles_quake.integer)
1242 color = particlepalette[ramp3[r]];
1243 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, NULL);
1247 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, NULL);
1248 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, NULL);
1251 else if (effectnameindex == EFFECT_TR_GRENADE)
1253 if (cl_particles_quake.integer)
1256 color = particlepalette[ramp3[r]];
1257 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, NULL);
1261 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, NULL);
1264 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1266 if (cl_particles_quake.integer)
1269 color = particlepalette[52 + (rand()&7)];
1270 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, NULL);
1271 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, NULL);
1273 else if (gamemode == GAME_GOODVSBAD2)
1276 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, NULL);
1280 color = particlepalette[20 + (rand()&7)];
1281 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, NULL);
1284 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1286 if (cl_particles_quake.integer)
1289 color = particlepalette[230 + (rand()&7)];
1290 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, NULL);
1291 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, NULL);
1295 color = particlepalette[226 + (rand()&7)];
1296 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, NULL);
1299 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1301 if (cl_particles_quake.integer)
1303 color = particlepalette[152 + (rand()&3)];
1304 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, NULL);
1306 else if (gamemode == GAME_GOODVSBAD2)
1309 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, NULL);
1311 else if (gamemode == GAME_PRYDON)
1314 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, NULL);
1317 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, NULL);
1319 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1322 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, NULL);
1324 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1327 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, NULL);
1329 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1330 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, NULL);
1334 if (effectnameindex == EFFECT_TR_ROCKET)
1335 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, NULL);
1336 else if (effectnameindex == EFFECT_TR_GRENADE)
1337 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, NULL);
1339 // advance to next time and position
1342 VectorMA (pos, dec, dir, pos);
1345 ent->persistent.trail_time = len;
1348 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1351 // this is also called on point effects with spawndlight = true and
1352 // spawnparticles = true
1353 // it is called CL_ParticleTrail because most code does not want to supply
1354 // these parameters, only trail handling does
1355 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, float tintmins[4], float tintmaxs[4])
1357 qboolean found = false;
1358 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1360 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1361 return; // no such effect
1363 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1365 int effectinfoindex;
1368 particleeffectinfo_t *info;
1375 qboolean underwater;
1376 qboolean immediatebloodstain;
1378 float avgtint[4], tint[4], tintlerp;
1379 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1380 VectorLerp(originmins, 0.5, originmaxs, center);
1381 supercontents = CL_PointSuperContents(center);
1382 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1383 VectorSubtract(originmaxs, originmins, traildir);
1384 traillen = VectorLength(traildir);
1385 VectorNormalize(traildir);
1388 Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint);
1392 Vector4Set(avgtint, 1, 1, 1, 1);
1394 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1396 if (info->effectnameindex == effectnameindex)
1399 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1401 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1404 // spawn a dlight if requested
1405 if (info->lightradiusstart > 0 && spawndlight)
1407 matrix4x4_t tempmatrix;
1408 if (info->trailspacing > 0)
1409 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1411 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1412 if (info->lighttime > 0 && info->lightradiusfade > 0)
1414 // light flash (explosion, etc)
1415 // called when effect starts
1416 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1418 else if (r_refdef.scene.numlights < MAX_DLIGHTS)
1421 // called by CL_LinkNetworkEntity
1422 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1423 rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3];
1424 rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3];
1425 rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3];
1426 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1427 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1431 if (!spawnparticles)
1436 if (info->tex[1] > info->tex[0])
1438 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1439 tex = min(tex, info->tex[1] - 1);
1441 if(info->staintex[0] < 0)
1442 staintex = info->staintex[0];
1445 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1446 staintex = min(staintex, info->staintex[1] - 1);
1448 if (info->particletype == pt_decal)
1449 CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]);
1450 else if (info->orientation == PARTICLE_HBEAM)
1451 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, tintmins ? avgtint : NULL);
1454 if (!cl_particles.integer)
1456 switch (info->particletype)
1458 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1459 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1460 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1461 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1462 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1463 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1466 VectorCopy(originmins, trailpos);
1467 if (info->trailspacing > 0)
1469 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value * pcount;
1470 trailstep = info->trailspacing / cl_particles_quality.value / max(0.001, pcount);
1471 immediatebloodstain = false;
1475 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1477 immediatebloodstain =
1478 ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood))
1480 ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex);
1482 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1483 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1485 if (info->tex[1] > info->tex[0])
1487 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1488 tex = min(tex, info->tex[1] - 1);
1492 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1493 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1494 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1498 tintlerp = lhrandom(0, 1);
1499 Vector4Lerp(tintmins, tintlerp, tintmaxs, tint);
1502 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]), tintmins ? tint : NULL);
1503 if (immediatebloodstain && part)
1505 immediatebloodstain = false;
1506 CL_ImmediateBloodStain(part);
1509 VectorMA(trailpos, trailstep, traildir, trailpos);
1516 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1519 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)
1521 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL);
1529 void CL_EntityParticles (const entity_t *ent)
1532 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1533 static vec3_t avelocities[NUMVERTEXNORMALS];
1534 if (!cl_particles.integer) return;
1535 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1537 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1539 if (!avelocities[0][0])
1540 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1541 avelocities[0][i] = lhrandom(0, 2.55);
1543 for (i = 0;i < NUMVERTEXNORMALS;i++)
1545 yaw = cl.time * avelocities[i][0];
1546 pitch = cl.time * avelocities[i][1];
1547 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1548 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1549 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1550 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, NULL);
1555 void CL_ReadPointFile_f (void)
1557 vec3_t org, leakorg;
1559 char *pointfile = NULL, *pointfilepos, *t, tchar;
1560 char name[MAX_QPATH];
1565 dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension);
1566 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1569 Con_Printf("Could not open %s\n", name);
1573 Con_Printf("Reading %s...\n", name);
1574 VectorClear(leakorg);
1577 pointfilepos = pointfile;
1578 while (*pointfilepos)
1580 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1585 while (*t && *t != '\n' && *t != '\r')
1589 #if _MSC_VER >= 1400
1590 #define sscanf sscanf_s
1592 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1598 VectorCopy(org, leakorg);
1601 if (cl.num_particles < cl.max_particles - 3)
1604 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, NULL);
1607 Mem_Free(pointfile);
1608 VectorCopy(leakorg, org);
1609 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1611 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, NULL);
1612 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, NULL);
1613 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, NULL);
1618 CL_ParseParticleEffect
1620 Parse an effect out of the server message
1623 void CL_ParseParticleEffect (void)
1626 int i, count, msgcount, color;
1628 MSG_ReadVector(org, cls.protocol);
1629 for (i=0 ; i<3 ; i++)
1630 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1631 msgcount = MSG_ReadByte ();
1632 color = MSG_ReadByte ();
1634 if (msgcount == 255)
1639 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1644 CL_ParticleExplosion
1648 void CL_ParticleExplosion (const vec3_t org)
1654 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1655 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1657 if (cl_particles_quake.integer)
1659 for (i = 0;i < 1024;i++)
1665 color = particlepalette[ramp1[r]];
1666 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, NULL);
1670 color = particlepalette[ramp2[r]];
1671 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, NULL);
1677 i = CL_PointSuperContents(org);
1678 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1680 if (cl_particles.integer && cl_particles_bubbles.integer)
1681 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1682 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, NULL);
1686 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1688 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1692 for (k = 0;k < 16;k++)
1695 VectorMA(org, 128, v2, v);
1696 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1697 if (trace.fraction >= 0.1)
1700 VectorSubtract(trace.endpos, org, v2);
1701 VectorScale(v2, 2.0f, v2);
1702 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, NULL);
1708 if (cl_particles_explosions_shell.integer)
1709 R_NewExplosion(org);
1714 CL_ParticleExplosion2
1718 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1721 if (!cl_particles.integer) return;
1723 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1725 k = particlepalette[colorStart + (i % colorLength)];
1726 if (cl_particles_quake.integer)
1727 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, NULL);
1729 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, NULL);
1733 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1736 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1737 if (cl_particles_sparks.integer)
1739 sparkcount *= cl_particles_quality.value;
1740 while(sparkcount-- > 0)
1741 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, NULL);
1745 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1748 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1749 if (cl_particles_smoke.integer)
1751 smokecount *= cl_particles_quality.value;
1752 while(smokecount-- > 0)
1753 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, NULL);
1757 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)
1761 if (!cl_particles.integer) return;
1762 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1764 count = (int)(count * cl_particles_quality.value);
1767 k = particlepalette[colorbase + (rand()&3)];
1768 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, NULL);
1772 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1775 float minz, maxz, lifetime = 30;
1777 if (!cl_particles.integer) return;
1778 if (dir[2] < 0) // falling
1780 minz = maxs[2] + dir[2] * 0.1;
1783 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1788 maxz = maxs[2] + dir[2] * 0.1;
1790 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1793 count = (int)(count * cl_particles_quality.value);
1798 if (!cl_particles_rain.integer) break;
1799 count *= 4; // ick, this should be in the mod or maps?
1803 k = particlepalette[colorbase + (rand()&3)];
1804 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1805 if (gamemode == GAME_GOODVSBAD2)
1806 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, NULL);
1808 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, NULL);
1812 if (!cl_particles_snow.integer) break;
1815 k = particlepalette[colorbase + (rand()&3)];
1816 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1817 if (gamemode == GAME_GOODVSBAD2)
1818 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, NULL);
1820 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, NULL);
1824 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1828 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1829 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1830 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1831 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1833 #define PARTICLETEXTURESIZE 64
1834 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1836 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1840 dz = 1 - (dx*dx+dy*dy);
1841 if (dz > 0) // it does hit the sphere
1845 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1846 VectorNormalize(normal);
1847 dot = DotProduct(normal, light);
1848 if (dot > 0.5) // interior reflection
1849 f += ((dot * 2) - 1);
1850 else if (dot < -0.5) // exterior reflection
1851 f += ((dot * -2) - 1);
1853 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1854 VectorNormalize(normal);
1855 dot = DotProduct(normal, light);
1856 if (dot > 0.5) // interior reflection
1857 f += ((dot * 2) - 1);
1858 else if (dot < -0.5) // exterior reflection
1859 f += ((dot * -2) - 1);
1861 f += 16; // just to give it a haze so you can see the outline
1862 f = bound(0, f, 255);
1863 return (unsigned char) f;
1869 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1870 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1872 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1873 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1874 *width = particlefontcellwidth;
1875 *height = particlefontcellheight;
1878 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1880 int basex, basey, w, h, y;
1881 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1882 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1883 Sys_Error("invalid particle texture size for autogenerating");
1884 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1885 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1888 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1891 float cx, cy, dx, dy, f, iradius;
1893 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1894 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1895 iradius = 1.0f / radius;
1896 alpha *= (1.0f / 255.0f);
1897 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1899 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1903 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1908 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1909 d[0] += (int)(f * (blue - d[0]));
1910 d[1] += (int)(f * (green - d[1]));
1911 d[2] += (int)(f * (red - d[2]));
1917 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1920 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1922 data[0] = bound(minb, data[0], maxb);
1923 data[1] = bound(ming, data[1], maxg);
1924 data[2] = bound(minr, data[2], maxr);
1928 void particletextureinvert(unsigned char *data)
1931 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1933 data[0] = 255 - data[0];
1934 data[1] = 255 - data[1];
1935 data[2] = 255 - data[2];
1939 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1940 static void R_InitBloodTextures (unsigned char *particletexturedata)
1943 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1944 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1947 for (i = 0;i < 8;i++)
1949 memset(data, 255, datasize);
1950 for (k = 0;k < 24;k++)
1951 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1952 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1953 particletextureinvert(data);
1954 setuptex(tex_bloodparticle[i], data, particletexturedata);
1958 for (i = 0;i < 8;i++)
1960 memset(data, 255, datasize);
1962 for (j = 1;j < 10;j++)
1963 for (k = min(j, m - 1);k < m;k++)
1964 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1965 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1966 particletextureinvert(data);
1967 setuptex(tex_blooddecal[i], data, particletexturedata);
1973 //uncomment this to make engine save out particle font to a tga file when run
1974 //#define DUMPPARTICLEFONT
1976 static void R_InitParticleTexture (void)
1978 int x, y, d, i, k, m;
1979 int basex, basey, w, h;
1980 float dx, dy, f, s1, t1, s2, t2;
1983 fs_offset_t filesize;
1984 char texturename[MAX_QPATH];
1986 // a note: decals need to modulate (multiply) the background color to
1987 // properly darken it (stain), and they need to be able to alpha fade,
1988 // this is a very difficult challenge because it means fading to white
1989 // (no change to background) rather than black (darkening everything
1990 // behind the whole decal polygon), and to accomplish this the texture is
1991 // inverted (dark red blood on white background becomes brilliant cyan
1992 // and white on black background) so we can alpha fade it to black, then
1993 // we invert it again during the blendfunc to make it work...
1995 #ifndef DUMPPARTICLEFONT
1996 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1999 particlefonttexture = decalskinframe->base;
2000 // TODO maybe allow custom grid size?
2001 particlefontwidth = image_width;
2002 particlefontheight = image_height;
2003 particlefontcellwidth = image_width / 8;
2004 particlefontcellheight = image_height / 8;
2005 particlefontcols = 8;
2006 particlefontrows = 8;
2011 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2012 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
2013 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
2014 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2015 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
2017 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
2018 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
2019 particlefontcols = 8;
2020 particlefontrows = 8;
2022 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
2025 for (i = 0;i < 8;i++)
2027 memset(data, 255, datasize);
2030 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
2031 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
2033 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2035 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2036 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2038 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2039 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
2041 d = (int)(d * (1-(dx*dx+dy*dy)));
2042 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
2043 d = bound(0, d, 255);
2044 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2051 setuptex(tex_smoke[i], data, particletexturedata);
2055 memset(data, 255, datasize);
2056 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2058 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2059 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2061 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2062 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2063 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2066 setuptex(tex_rainsplash, data, particletexturedata);
2069 memset(data, 255, datasize);
2070 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2072 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2073 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2075 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2076 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2077 d = bound(0, d, 255);
2078 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2081 setuptex(tex_particle, data, particletexturedata);
2084 memset(data, 255, datasize);
2085 light[0] = 1;light[1] = 1;light[2] = 1;
2086 VectorNormalize(light);
2087 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2089 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2090 // stretch upper half of bubble by +50% and shrink lower half by -50%
2091 // (this gives an elongated teardrop shape)
2093 dy = (dy - 0.5f) * 2.0f;
2095 dy = (dy - 0.5f) / 1.5f;
2096 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2098 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2099 // shrink bubble width to half
2101 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2104 setuptex(tex_raindrop, data, particletexturedata);
2107 memset(data, 255, datasize);
2108 light[0] = 1;light[1] = 1;light[2] = 1;
2109 VectorNormalize(light);
2110 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2112 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2113 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2115 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2116 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2119 setuptex(tex_bubble, data, particletexturedata);
2121 // Blood particles and blood decals
2122 R_InitBloodTextures (particletexturedata);
2125 for (i = 0;i < 8;i++)
2127 memset(data, 255, datasize);
2128 for (k = 0;k < 12;k++)
2129 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2130 for (k = 0;k < 3;k++)
2131 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2132 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2133 particletextureinvert(data);
2134 setuptex(tex_bulletdecal[i], data, particletexturedata);
2137 #ifdef DUMPPARTICLEFONT
2138 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2141 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2142 particlefonttexture = decalskinframe->base;
2144 Mem_Free(particletexturedata);
2149 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2151 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2152 particletexture[i].texture = particlefonttexture;
2153 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2154 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2155 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2156 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2159 #ifndef DUMPPARTICLEFONT
2160 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2161 if (!particletexture[tex_beam].texture)
2164 unsigned char noise3[64][64], data2[64][16][4];
2166 fractalnoise(&noise3[0][0], 64, 4);
2168 for (y = 0;y < 64;y++)
2170 dy = (y - 0.5f*64) / (64*0.5f-1);
2171 for (x = 0;x < 16;x++)
2173 dx = (x - 0.5f*16) / (16*0.5f-2);
2174 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2175 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2176 data2[y][x][3] = 255;
2180 #ifdef DUMPPARTICLEFONT
2181 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2183 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL);
2185 particletexture[tex_beam].s1 = 0;
2186 particletexture[tex_beam].t1 = 0;
2187 particletexture[tex_beam].s2 = 1;
2188 particletexture[tex_beam].t2 = 1;
2190 // now load an texcoord/texture override file
2191 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2198 if(!COM_ParseToken_Simple(&bufptr, true, false))
2200 if(!strcmp(com_token, "\n"))
2201 continue; // empty line
2202 i = atoi(com_token);
2210 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2212 s1 = atof(com_token);
2213 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2215 t1 = atof(com_token);
2216 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2218 s2 = atof(com_token);
2219 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2221 t2 = atof(com_token);
2222 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2223 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2224 strlcpy(texturename, com_token, sizeof(texturename));
2231 strlcpy(texturename, com_token, sizeof(texturename));
2234 if (!texturename[0])
2236 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2239 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2241 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2244 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2245 particletexture[i].s1 = s1;
2246 particletexture[i].t1 = t1;
2247 particletexture[i].s2 = s2;
2248 particletexture[i].t2 = t2;
2254 static void r_part_start(void)
2257 // generate particlepalette for convenience from the main one
2258 for (i = 0;i < 256;i++)
2259 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2260 particletexturepool = R_AllocTexturePool();
2261 R_InitParticleTexture ();
2262 CL_Particles_LoadEffectInfo();
2265 static void r_part_shutdown(void)
2267 R_FreeTexturePool(&particletexturepool);
2270 static void r_part_newmap(void)
2273 R_SkinFrame_MarkUsed(decalskinframe);
2274 CL_Particles_LoadEffectInfo();
2277 #define BATCHSIZE 256
2278 unsigned short particle_elements[BATCHSIZE*6];
2279 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2281 void R_Particles_Init (void)
2284 for (i = 0;i < BATCHSIZE;i++)
2286 particle_elements[i*6+0] = i*4+0;
2287 particle_elements[i*6+1] = i*4+1;
2288 particle_elements[i*6+2] = i*4+2;
2289 particle_elements[i*6+3] = i*4+0;
2290 particle_elements[i*6+4] = i*4+2;
2291 particle_elements[i*6+5] = i*4+3;
2294 Cvar_RegisterVariable(&r_drawparticles);
2295 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2296 Cvar_RegisterVariable(&r_drawdecals);
2297 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2298 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2301 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2303 int surfacelistindex;
2305 float *v3f, *t2f, *c4f;
2306 particletexture_t *tex;
2307 float right[3], up[3], size, ca;
2308 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2309 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2311 RSurf_ActiveWorldEntity();
2313 r_refdef.stats.drawndecals += numsurfaces;
2314 R_Mesh_ResetTextureState();
2315 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2316 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2317 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2318 GL_DepthMask(false);
2319 GL_DepthRange(0, 1);
2320 GL_PolygonOffset(0, 0);
2322 GL_CullFace(GL_NONE);
2324 // generate all the vertices at once
2325 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2327 d = cl.decals + surfacelist[surfacelistindex];
2330 c4f = particle_color4f + 16*surfacelistindex;
2331 ca = d->alpha * alphascale;
2332 // ensure alpha multiplier saturates properly
2333 if (ca > 1.0f / 256.0f)
2335 if (r_refdef.fogenabled)
2336 ca *= RSurf_FogVertex(d->org);
2337 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2338 Vector4Copy(c4f, c4f + 4);
2339 Vector4Copy(c4f, c4f + 8);
2340 Vector4Copy(c4f, c4f + 12);
2342 // calculate vertex positions
2343 size = d->size * cl_particles_size.value;
2344 VectorVectors(d->normal, right, up);
2345 VectorScale(right, size, right);
2346 VectorScale(up, size, up);
2347 v3f = particle_vertex3f + 12*surfacelistindex;
2348 v3f[ 0] = d->org[0] - right[0] - up[0];
2349 v3f[ 1] = d->org[1] - right[1] - up[1];
2350 v3f[ 2] = d->org[2] - right[2] - up[2];
2351 v3f[ 3] = d->org[0] - right[0] + up[0];
2352 v3f[ 4] = d->org[1] - right[1] + up[1];
2353 v3f[ 5] = d->org[2] - right[2] + up[2];
2354 v3f[ 6] = d->org[0] + right[0] + up[0];
2355 v3f[ 7] = d->org[1] + right[1] + up[1];
2356 v3f[ 8] = d->org[2] + right[2] + up[2];
2357 v3f[ 9] = d->org[0] + right[0] - up[0];
2358 v3f[10] = d->org[1] + right[1] - up[1];
2359 v3f[11] = d->org[2] + right[2] - up[2];
2361 // calculate texcoords
2362 tex = &particletexture[d->texnum];
2363 t2f = particle_texcoord2f + 8*surfacelistindex;
2364 t2f[0] = tex->s1;t2f[1] = tex->t2;
2365 t2f[2] = tex->s1;t2f[3] = tex->t1;
2366 t2f[4] = tex->s2;t2f[5] = tex->t1;
2367 t2f[6] = tex->s2;t2f[7] = tex->t2;
2370 // now render the decals all at once
2371 // (this assumes they all use one particle font texture!)
2372 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2373 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2374 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2377 void R_DrawDecals (void)
2380 int drawdecals = r_drawdecals.integer;
2385 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2387 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2388 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2390 // LordHavoc: early out conditions
2394 decalfade = frametime * 256 / cl_decals_fadetime.value;
2395 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2396 drawdist2 = drawdist2*drawdist2;
2398 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2400 if (!decal->typeindex)
2403 if (killsequence - decal->decalsequence > 0)
2406 if (cl.time > decal->time2 + cl_decals_time.value)
2408 decal->alpha -= decalfade;
2409 if (decal->alpha <= 0)
2415 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2417 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2418 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2424 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2430 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))
2431 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2434 decal->typeindex = 0;
2435 if (cl.free_decal > i)
2439 // reduce cl.num_decals if possible
2440 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2443 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2445 decal_t *olddecals = cl.decals;
2446 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2447 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2448 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2449 Mem_Free(olddecals);
2452 r_refdef.stats.totaldecals = cl.num_decals;
2455 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2457 int surfacelistindex;
2458 int batchstart, batchcount;
2459 const particle_t *p;
2461 rtexture_t *texture;
2462 float *v3f, *t2f, *c4f;
2463 particletexture_t *tex;
2464 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2465 float ambient[3], diffuse[3], diffusenormal[3];
2466 float spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4, baseright[3], baseup[3];
2467 vec4_t colormultiplier;
2469 RSurf_ActiveWorldEntity();
2471 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));
2473 r_refdef.stats.particles += numsurfaces;
2474 R_Mesh_ResetTextureState();
2475 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2476 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2477 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2478 GL_DepthMask(false);
2479 GL_DepthRange(0, 1);
2480 GL_PolygonOffset(0, 0);
2482 GL_AlphaTest(false);
2483 GL_CullFace(GL_NONE);
2485 spintime = r_refdef.scene.time;
2487 // first generate all the vertices at once
2488 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2490 p = cl.particles + surfacelist[surfacelistindex];
2492 blendmode = (pblend_t)p->blendmode;
2496 case PBLEND_INVALID:
2498 alpha = p->alpha * colormultiplier[3];
2499 // ensure alpha multiplier saturates properly
2502 // additive and modulate can just fade out in fog (this is correct)
2503 if (r_refdef.fogenabled)
2504 alpha *= RSurf_FogVertex(p->org);
2505 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2506 alpha *= 1.0f / 256.0f;
2507 c4f[0] = p->color[0] * alpha;
2508 c4f[1] = p->color[1] * alpha;
2509 c4f[2] = p->color[2] * alpha;
2513 alpha = p->alpha * colormultiplier[3];
2514 // ensure alpha multiplier saturates properly
2517 // additive and modulate can just fade out in fog (this is correct)
2518 if (r_refdef.fogenabled)
2519 alpha *= RSurf_FogVertex(p->org);
2520 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2521 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2522 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2523 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2527 c4f[0] = p->color[0] * colormultiplier[0];
2528 c4f[1] = p->color[1] * colormultiplier[1];
2529 c4f[2] = p->color[2] * colormultiplier[2];
2530 c4f[3] = p->alpha * colormultiplier[3];
2531 // note: lighting is not cheap!
2532 if (particletype[p->typeindex].lighting)
2534 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2535 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2536 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2537 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2539 // mix in the fog color
2540 if (r_refdef.fogenabled)
2542 fog = RSurf_FogVertex(p->org);
2544 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2545 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2546 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2550 // copy the color into the other three vertices
2551 Vector4Copy(c4f, c4f + 4);
2552 Vector4Copy(c4f, c4f + 8);
2553 Vector4Copy(c4f, c4f + 12);
2555 size = p->size * cl_particles_size.value;
2556 tex = &particletexture[p->texnum];
2557 switch(p->orientation)
2559 // case PARTICLE_INVALID:
2560 case PARTICLE_BILLBOARD:
2561 if (p->angle + p->spin)
2563 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2564 spinsin = sin(spinrad) * size;
2565 spincos = cos(spinrad) * size;
2566 spinm1 = -p->stretch * spincos;
2569 spinm4 = -p->stretch * spincos;
2570 VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right);
2571 VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up);
2575 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2576 VectorScale(r_refdef.view.up, size, up);
2579 v3f[ 0] = p->org[0] - right[0] - up[0];
2580 v3f[ 1] = p->org[1] - right[1] - up[1];
2581 v3f[ 2] = p->org[2] - right[2] - up[2];
2582 v3f[ 3] = p->org[0] - right[0] + up[0];
2583 v3f[ 4] = p->org[1] - right[1] + up[1];
2584 v3f[ 5] = p->org[2] - right[2] + up[2];
2585 v3f[ 6] = p->org[0] + right[0] + up[0];
2586 v3f[ 7] = p->org[1] + right[1] + up[1];
2587 v3f[ 8] = p->org[2] + right[2] + up[2];
2588 v3f[ 9] = p->org[0] + right[0] - up[0];
2589 v3f[10] = p->org[1] + right[1] - up[1];
2590 v3f[11] = p->org[2] + right[2] - up[2];
2591 t2f[0] = tex->s1;t2f[1] = tex->t2;
2592 t2f[2] = tex->s1;t2f[3] = tex->t1;
2593 t2f[4] = tex->s2;t2f[5] = tex->t1;
2594 t2f[6] = tex->s2;t2f[7] = tex->t2;
2596 case PARTICLE_ORIENTED_DOUBLESIDED:
2597 VectorVectors(p->vel, baseright, baseup);
2598 if (p->angle + p->spin)
2600 spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f);
2601 spinsin = sin(spinrad) * size;
2602 spincos = cos(spinrad) * size;
2603 spinm1 = p->stretch * spincos;
2606 spinm4 = p->stretch * spincos;
2607 VectorMAM(spinm1, baseright, spinm2, baseup, right);
2608 VectorMAM(spinm3, baseright, spinm4, baseup, up);
2612 VectorScale(baseright, size * p->stretch, right);
2613 VectorScale(baseup, size, up);
2615 v3f[ 0] = p->org[0] - right[0] - up[0];
2616 v3f[ 1] = p->org[1] - right[1] - up[1];
2617 v3f[ 2] = p->org[2] - right[2] - up[2];
2618 v3f[ 3] = p->org[0] - right[0] + up[0];
2619 v3f[ 4] = p->org[1] - right[1] + up[1];
2620 v3f[ 5] = p->org[2] - right[2] + up[2];
2621 v3f[ 6] = p->org[0] + right[0] + up[0];
2622 v3f[ 7] = p->org[1] + right[1] + up[1];
2623 v3f[ 8] = p->org[2] + right[2] + up[2];
2624 v3f[ 9] = p->org[0] + right[0] - up[0];
2625 v3f[10] = p->org[1] + right[1] - up[1];
2626 v3f[11] = p->org[2] + right[2] - up[2];
2627 t2f[0] = tex->s1;t2f[1] = tex->t2;
2628 t2f[2] = tex->s1;t2f[3] = tex->t1;
2629 t2f[4] = tex->s2;t2f[5] = tex->t1;
2630 t2f[6] = tex->s2;t2f[7] = tex->t2;
2632 case PARTICLE_SPARK:
2633 len = VectorLength(p->vel);
2634 VectorNormalize2(p->vel, up);
2635 lenfactor = p->stretch * 0.04 * len;
2636 if(lenfactor < size * 0.5)
2637 lenfactor = size * 0.5;
2638 VectorMA(p->org, -lenfactor, up, v);
2639 VectorMA(p->org, lenfactor, up, up2);
2640 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2641 t2f[0] = tex->s1;t2f[1] = tex->t2;
2642 t2f[2] = tex->s1;t2f[3] = tex->t1;
2643 t2f[4] = tex->s2;t2f[5] = tex->t1;
2644 t2f[6] = tex->s2;t2f[7] = tex->t2;
2646 case PARTICLE_VBEAM:
2647 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2648 VectorSubtract(p->vel, p->org, up);
2649 VectorNormalize(up);
2650 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2651 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2652 t2f[0] = tex->s2;t2f[1] = v[0];
2653 t2f[2] = tex->s1;t2f[3] = v[0];
2654 t2f[4] = tex->s1;t2f[5] = v[1];
2655 t2f[6] = tex->s2;t2f[7] = v[1];
2657 case PARTICLE_HBEAM:
2658 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2659 VectorSubtract(p->vel, p->org, up);
2660 VectorNormalize(up);
2661 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2662 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2663 t2f[0] = v[0];t2f[1] = tex->t1;
2664 t2f[2] = v[0];t2f[3] = tex->t2;
2665 t2f[4] = v[1];t2f[5] = tex->t2;
2666 t2f[6] = v[1];t2f[7] = tex->t1;
2671 // now render batches of particles based on blendmode and texture
2672 blendmode = PBLEND_INVALID;
2676 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2678 p = cl.particles + surfacelist[surfacelistindex];
2680 if (blendmode != p->blendmode)
2682 blendmode = (pblend_t)p->blendmode;
2686 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2688 case PBLEND_INVALID:
2690 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2693 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2697 if (texture != particletexture[p->texnum].texture)
2699 texture = particletexture[p->texnum].texture;
2700 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2703 // iterate until we find a change in settings
2704 batchstart = surfacelistindex++;
2705 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2707 p = cl.particles + surfacelist[surfacelistindex];
2708 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2712 batchcount = surfacelistindex - batchstart;
2713 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2717 void R_DrawParticles (void)
2720 int drawparticles = r_drawparticles.integer;
2721 float minparticledist;
2723 float gravity, frametime, f, dist, oldorg[3];
2729 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2730 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2732 // LordHavoc: early out conditions
2733 if (!cl.num_particles)
2736 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2737 gravity = frametime * cl.movevars_gravity;
2738 update = frametime > 0;
2739 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2740 drawdist2 = drawdist2*drawdist2;
2742 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2746 if (cl.free_particle > i)
2747 cl.free_particle = i;
2753 if (p->delayedspawn > cl.time)
2756 p->size += p->sizeincrease * frametime;
2757 p->alpha -= p->alphafade * frametime;
2759 if (p->alpha <= 0 || p->die <= cl.time)
2762 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2764 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2766 if (p->typeindex == pt_blood)
2767 p->size += frametime * 8;
2769 p->vel[2] -= p->gravity * gravity;
2770 f = 1.0f - min(p->liquidfriction * frametime, 1);
2771 VectorScale(p->vel, f, p->vel);
2775 p->vel[2] -= p->gravity * gravity;
2778 f = 1.0f - min(p->airfriction * frametime, 1);
2779 VectorScale(p->vel, f, p->vel);
2783 VectorCopy(p->org, oldorg);
2784 VectorMA(p->org, frametime, p->vel, p->org);
2785 // if (p->bounce && cl.time >= p->delayedcollisions)
2786 if (p->bounce && cl_particles_collisions.integer)
2788 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);
2789 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2790 // or if the trace hit something flagged as NOIMPACT
2791 // then remove the particle
2792 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2794 VectorCopy(trace.endpos, p->org);
2795 // react if the particle hit something
2796 if (trace.fraction < 1)
2798 VectorCopy(trace.endpos, p->org);
2800 if (p->staintexnum >= 0)
2802 // blood - splash on solid
2803 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2806 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2807 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2808 if (cl_decals.integer)
2810 // create a decal for the blood splat
2811 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2812 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2817 if (p->typeindex == pt_blood)
2819 // blood - splash on solid
2820 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2822 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2824 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)));
2825 if (cl_decals.integer)
2827 // create a decal for the blood splat
2828 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);
2833 else if (p->bounce < 0)
2835 // bounce -1 means remove on impact
2840 // anything else - bounce off solid
2841 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2842 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2843 if (DotProduct(p->vel, p->vel) < 0.03)
2844 VectorClear(p->vel);
2850 if (p->typeindex != pt_static)
2852 switch (p->typeindex)
2854 case pt_entityparticle:
2855 // particle that removes itself after one rendered frame
2862 a = CL_PointSuperContents(p->org);
2863 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2867 a = CL_PointSuperContents(p->org);
2868 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2872 a = CL_PointSuperContents(p->org);
2873 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2877 if (cl.time > p->time2)
2880 p->time2 = cl.time + (rand() & 3) * 0.1;
2881 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2882 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2884 a = CL_PointSuperContents(p->org);
2885 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2893 else if (p->delayedspawn > cl.time)
2897 // don't render particles too close to the view (they chew fillrate)
2898 // also don't render particles behind the view (useless)
2899 // further checks to cull to the frustum would be too slow here
2900 switch(p->typeindex)
2903 // beams have no culling
2904 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2907 if(cl_particles_visculling.integer)
2908 if (!r_refdef.viewcache.world_novis)
2909 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2911 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2913 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2916 // anything else just has to be in front of the viewer and visible at this distance
2917 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2918 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2925 if (cl.free_particle > i)
2926 cl.free_particle = i;
2929 // reduce cl.num_particles if possible
2930 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2933 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2935 particle_t *oldparticles = cl.particles;
2936 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2937 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2938 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2939 Mem_Free(oldparticles);