2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 // must match ptype_t values
28 particletype_t particletype[pt_total] =
30 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
31 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
32 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
33 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
34 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
35 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
36 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
37 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
38 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
39 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
41 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
42 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
45 #define PARTICLEEFFECT_UNDERWATER 1
46 #define PARTICLEEFFECT_NOTUNDERWATER 2
48 typedef struct particleeffectinfo_s
50 int effectnameindex; // which effect this belongs to
51 // PARTICLEEFFECT_* bits
53 // blood effects may spawn very few particles, so proper fraction-overflow
54 // handling is very important, this variable keeps track of the fraction
55 double particleaccumulator;
56 // the math is: countabsolute + requestedcount * countmultiplier * quality
57 // absolute number of particles to spawn, often used for decals
58 // (unaffected by quality and requestedcount)
60 // multiplier for the number of particles CL_ParticleEffect was told to
61 // spawn, most effects do not really have a count and hence use 1, so
62 // this is often the actual count to spawn, not merely a multiplier
63 float countmultiplier;
64 // if > 0 this causes the particle to spawn in an evenly spaced line from
65 // originmins to originmaxs (causing them to describe a trail, not a box)
67 // type of particle to spawn (defines some aspects of behavior)
69 // blending mode used on this particle type
71 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
72 porientation_t orientation;
73 // range of colors to choose from in hex RRGGBB (like HTML color tags),
74 // randomly interpolated at spawn
75 unsigned int color[2];
76 // a random texture is chosen in this range (note the second value is one
77 // past the last choosable, so for example 8,16 chooses any from 8 up and
79 // if start and end of the range are the same, no randomization is done
81 // range of size values randomly chosen when spawning, plus size increase over time
83 // range of alpha values randomly chosen when spawning, plus alpha fade
85 // how long the particle should live (note it is also removed if alpha drops to 0)
87 // how much gravity affects this particle (negative makes it fly up!)
89 // how much bounce the particle has when it hits a surface
90 // if negative the particle is removed on impact
92 // if in air this friction is applied
93 // if negative the particle accelerates
95 // if in liquid (water/slime/lava) this friction is applied
96 // if negative the particle accelerates
98 // these offsets are added to the values given to particleeffect(), and
99 // then an ellipsoid-shaped jitter is added as defined by these
100 // (they are the 3 radii)
102 // stretch velocity factor (used for sparks)
103 float originoffset[3];
104 float velocityoffset[3];
105 float originjitter[3];
106 float velocityjitter[3];
107 float velocitymultiplier;
108 // an effect can also spawn a dlight
109 float lightradiusstart;
110 float lightradiusfade;
113 qboolean lightshadow;
115 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
120 particleeffectinfo_t;
122 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
125 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
127 static int particlepalette[256];
129 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
130 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
131 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
132 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
133 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
134 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
135 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
136 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
137 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
138 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
139 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
140 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
141 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
142 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
143 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
144 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
145 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
146 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
147 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
148 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
149 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
150 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
151 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
152 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
153 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
154 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
155 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
156 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
157 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
158 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
159 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
160 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
163 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
164 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
165 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
167 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
169 // particletexture_t is a rectangle in the particlefonttexture
170 typedef struct particletexture_s
173 float s1, t1, s2, t2;
177 static rtexturepool_t *particletexturepool;
178 static rtexture_t *particlefonttexture;
179 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
180 skinframe_t *decalskinframe;
182 // texture numbers in particle font
183 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
184 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
185 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
186 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
187 static const int tex_rainsplash = 32;
188 static const int tex_particle = 63;
189 static const int tex_bubble = 62;
190 static const int tex_raindrop = 61;
191 static const int tex_beam = 60;
193 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
194 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
195 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
196 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
197 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
198 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
199 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"};
200 cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"};
201 cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"};
202 cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"};
203 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
204 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
205 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
206 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
207 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
208 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
209 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
210 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
211 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
212 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
213 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
214 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
215 cvar_t cl_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)"};
216 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
217 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
218 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
219 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
220 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
221 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)"};
222 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
223 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
224 cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"};
227 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename)
233 particleeffectinfo_t *info = NULL;
234 const char *text = textstart;
236 effectinfoindex = -1;
237 for (linenumber = 1;;linenumber++)
240 for (arrayindex = 0;arrayindex < 16;arrayindex++)
241 argv[arrayindex][0] = 0;
244 if (!COM_ParseToken_Simple(&text, true, false))
246 if (!strcmp(com_token, "\n"))
250 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
256 #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;}
257 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
258 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
259 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
260 #define readfloat(var) checkparms(2);var = atof(argv[1])
261 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
262 if (!strcmp(argv[0], "effect"))
267 if (effectinfoindex >= 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 + effectinfoindex;
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 else if (info == NULL)
322 Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]);
325 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
326 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
327 else if (!strcmp(argv[0], "type"))
330 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
331 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
332 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
333 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
334 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
335 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
336 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
337 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
338 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
339 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
340 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
341 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
342 else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]);
343 info->blendmode = particletype[info->particletype].blendmode;
344 info->orientation = particletype[info->particletype].orientation;
346 else if (!strcmp(argv[0], "blend"))
349 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
350 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
351 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
352 else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]);
354 else if (!strcmp(argv[0], "orientation"))
357 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
358 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
359 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
360 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
361 else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]);
363 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
364 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
365 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
366 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
367 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
368 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
369 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
370 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
371 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
372 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
373 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
374 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
375 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
376 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
377 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
378 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
379 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
380 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
381 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
382 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
383 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
384 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
385 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
386 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
387 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
388 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
389 else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);}
390 else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);}
391 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
392 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; }
394 Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]);
403 int CL_ParticleEffectIndexForName(const char *name)
406 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
407 if (!strcmp(particleeffectname[i], name))
412 const char *CL_ParticleEffectNameForIndex(int i)
414 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
416 return particleeffectname[i];
419 // MUST match effectnameindex_t in client.h
420 static const char *standardeffectnames[EFFECT_TOTAL] =
444 "TE_TEI_BIGEXPLOSION",
460 void CL_Particles_LoadEffectInfo(void)
464 unsigned char *filedata;
465 fs_offset_t filesize;
466 char filename[MAX_QPATH];
467 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
468 memset(particleeffectname, 0, sizeof(particleeffectname));
469 for (i = 0;i < EFFECT_TOTAL;i++)
470 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
471 for (filepass = 0;;filepass++)
474 dpsnprintf(filename, sizeof(filename), "effectinfo.txt");
475 else if (filepass == 1)
476 dpsnprintf(filename, sizeof(filename), "maps/%s_effectinfo.txt", cl.levelname);
479 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
482 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename);
492 void CL_ReadPointFile_f (void);
493 void CL_Particles_Init (void)
495 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)");
496 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map)");
498 Cvar_RegisterVariable (&cl_particles);
499 Cvar_RegisterVariable (&cl_particles_quality);
500 Cvar_RegisterVariable (&cl_particles_alpha);
501 Cvar_RegisterVariable (&cl_particles_size);
502 Cvar_RegisterVariable (&cl_particles_quake);
503 Cvar_RegisterVariable (&cl_particles_blood);
504 Cvar_RegisterVariable (&cl_particles_blood_alpha);
505 Cvar_RegisterVariable (&cl_particles_blood_decal_alpha);
506 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin);
507 Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax);
508 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
509 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
510 Cvar_RegisterVariable (&cl_particles_explosions_shell);
511 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
512 Cvar_RegisterVariable (&cl_particles_rain);
513 Cvar_RegisterVariable (&cl_particles_snow);
514 Cvar_RegisterVariable (&cl_particles_smoke);
515 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
516 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
517 Cvar_RegisterVariable (&cl_particles_sparks);
518 Cvar_RegisterVariable (&cl_particles_bubbles);
519 Cvar_RegisterVariable (&cl_particles_visculling);
520 Cvar_RegisterVariable (&cl_particles_collisions);
521 Cvar_RegisterVariable (&cl_decals);
522 Cvar_RegisterVariable (&cl_decals_visculling);
523 Cvar_RegisterVariable (&cl_decals_time);
524 Cvar_RegisterVariable (&cl_decals_fadetime);
525 Cvar_RegisterVariable (&cl_decals_newsystem);
526 Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier);
527 Cvar_RegisterVariable (&cl_decals_models);
528 Cvar_RegisterVariable (&cl_decals_bias);
529 Cvar_RegisterVariable (&cl_decals_max);
532 void CL_Particles_Shutdown (void)
536 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
537 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
539 // list of all 26 parameters:
540 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
541 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
542 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
543 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
544 // palpha - opacity of particle as 0-255 (can be more than 255)
545 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
546 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
547 // pgravity - how much effect gravity has on the particle (0-1)
548 // 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
549 // px,py,pz - starting origin of particle
550 // pvx,pvy,pvz - starting velocity of particle
551 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
552 // blendmode - one of the PBLEND_ values
553 // orientation - one of the PARTICLE_ values
554 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none)
555 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
556 // stainalpha: opacity of the stain as factor for alpha
557 // stainsize: size of the stain as factor for palpha
558 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)
563 if (!cl_particles.integer)
565 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
566 if (cl.free_particle >= cl.max_particles)
569 lifetime = palpha / min(1, palphafade);
570 part = &cl.particles[cl.free_particle++];
571 if (cl.num_particles < cl.free_particle)
572 cl.num_particles = cl.free_particle;
573 memset(part, 0, sizeof(*part));
574 VectorCopy(sortorigin, part->sortorigin);
575 part->typeindex = ptypeindex;
576 part->blendmode = blendmode;
577 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
579 particletexture_t *tex = &particletexture[ptex];
580 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
581 part->orientation = PARTICLE_VBEAM;
583 part->orientation = PARTICLE_HBEAM;
586 part->orientation = orientation;
587 l2 = (int)lhrandom(0.5, 256.5);
589 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
590 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
591 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
592 part->staintexnum = staintex;
593 if(staincolor1 >= 0 && staincolor2 >= 0)
595 l2 = (int)lhrandom(0.5, 256.5);
597 if(blendmode == PBLEND_INVMOD)
599 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
600 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
601 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
605 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
606 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
607 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
609 if(r > 0xFF) r = 0xFF;
610 if(g > 0xFF) g = 0xFF;
611 if(b > 0xFF) b = 0xFF;
615 r = part->color[0]; // -1 is shorthand for stain = particle color
619 part->staincolor[0] = r;
620 part->staincolor[1] = g;
621 part->staincolor[2] = b;
622 part->stainalpha = palpha * stainalpha / 256;
623 part->stainsize = psize * stainsize;
626 part->sizeincrease = psizeincrease;
627 part->alpha = palpha;
628 part->alphafade = palphafade;
629 part->gravity = pgravity;
630 part->bounce = pbounce;
631 part->stretch = stretch;
633 part->org[0] = px + originjitter * v[0];
634 part->org[1] = py + originjitter * v[1];
635 part->org[2] = pz + originjitter * v[2];
636 part->vel[0] = pvx + velocityjitter * v[0];
637 part->vel[1] = pvy + velocityjitter * v[1];
638 part->vel[2] = pvz + velocityjitter * v[2];
640 part->airfriction = pairfriction;
641 part->liquidfriction = pliquidfriction;
642 part->die = cl.time + lifetime;
643 // part->delayedcollisions = 0;
644 part->qualityreduction = pqualityreduction;
645 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
646 if (part->typeindex == pt_rain)
650 float lifetime = part->die - cl.time;
653 // turn raindrop into simple spark and create delayedspawn splash effect
654 part->typeindex = pt_spark;
656 VectorMA(part->org, lifetime, part->vel, endvec);
657 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
658 part->die = cl.time + lifetime * trace.fraction;
659 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);
662 part2->delayedspawn = part->die;
663 part2->die += part->die - cl.time;
664 for (i = rand() & 7;i < 10;i++)
666 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);
669 part2->delayedspawn = part->die;
670 part2->die += part->die - cl.time;
676 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
678 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
681 VectorMA(part->org, lifetime, part->vel, endvec);
682 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
683 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
690 static void CL_ImmediateBloodStain(particle_t *part)
695 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
696 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
698 VectorCopy(part->vel, v);
700 staintex = part->staintexnum;
701 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);
704 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
705 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
707 VectorCopy(part->vel, v);
709 staintex = tex_blooddecal[rand()&7];
710 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);
714 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
718 entity_render_t *ent = &cl.entities[hitent].render;
719 unsigned char color[3];
720 if (!cl_decals.integer)
722 if (!ent->allowdecals)
725 l2 = (int)lhrandom(0.5, 256.5);
727 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
728 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
729 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
731 if (cl_decals_newsystem.integer)
733 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);
737 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
738 if (cl.free_decal >= cl.max_decals)
740 decal = &cl.decals[cl.free_decal++];
741 if (cl.num_decals < cl.free_decal)
742 cl.num_decals = cl.free_decal;
743 memset(decal, 0, sizeof(*decal));
744 decal->decalsequence = cl.decalsequence++;
745 decal->typeindex = pt_decal;
746 decal->texnum = texnum;
747 VectorMA(org, cl_decals_bias.value, normal, decal->org);
748 VectorCopy(normal, decal->normal);
750 decal->alpha = alpha;
751 decal->time2 = cl.time;
752 decal->color[0] = color[0];
753 decal->color[1] = color[1];
754 decal->color[2] = color[2];
755 decal->owner = hitent;
756 decal->clusterindex = -1000; // no vis culling unless we're sure
759 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
760 decal->ownermodel = cl.entities[decal->owner].render.model;
761 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
762 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
766 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
768 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
770 decal->clusterindex = leaf->clusterindex;
775 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
778 float bestfrac, bestorg[3], bestnormal[3];
780 int besthitent = 0, hitent;
783 for (i = 0;i < 32;i++)
786 VectorMA(org, maxdist, org2, org2);
787 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
788 // take the closest trace result that doesn't end up hitting a NOMARKS
789 // surface (sky for example)
790 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
792 bestfrac = trace.fraction;
794 VectorCopy(trace.endpos, bestorg);
795 VectorCopy(trace.plane.normal, bestnormal);
799 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
802 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
803 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
804 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)
807 matrix4x4_t tempmatrix;
809 VectorLerp(originmins, 0.5, originmaxs, center);
810 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
811 if (effectnameindex == EFFECT_SVC_PARTICLE)
813 if (cl_particles.integer)
815 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
817 CL_ParticleExplosion(center);
818 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
819 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
822 count *= cl_particles_quality.value;
823 for (;count > 0;count--)
825 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
826 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);
831 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
832 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
833 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
834 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
835 else if (effectnameindex == EFFECT_TE_SPIKE)
837 if (cl_particles_bulletimpacts.integer)
839 if (cl_particles_quake.integer)
841 if (cl_particles_smoke.integer)
842 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
846 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
847 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
848 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);
852 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
853 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
855 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
857 if (cl_particles_bulletimpacts.integer)
859 if (cl_particles_quake.integer)
861 if (cl_particles_smoke.integer)
862 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
866 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
867 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
868 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);
872 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
873 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
874 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);
876 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
878 if (cl_particles_bulletimpacts.integer)
880 if (cl_particles_quake.integer)
882 if (cl_particles_smoke.integer)
883 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
887 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
888 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
889 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);
893 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
894 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
896 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
898 if (cl_particles_bulletimpacts.integer)
900 if (cl_particles_quake.integer)
902 if (cl_particles_smoke.integer)
903 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
907 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
908 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
909 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);
913 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
914 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
915 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);
917 else if (effectnameindex == EFFECT_TE_BLOOD)
919 if (!cl_particles_blood.integer)
921 if (cl_particles_quake.integer)
922 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
925 static double bloodaccumulator = 0;
926 qboolean immediatebloodstain = true;
927 //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);
928 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
929 for (;bloodaccumulator > 0;bloodaccumulator--)
931 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);
932 if (immediatebloodstain && part)
934 immediatebloodstain = false;
935 CL_ImmediateBloodStain(part);
940 else if (effectnameindex == EFFECT_TE_SPARK)
941 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
942 else if (effectnameindex == EFFECT_TE_PLASMABURN)
944 // plasma scorch mark
945 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
946 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
947 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
949 else if (effectnameindex == EFFECT_TE_GUNSHOT)
951 if (cl_particles_bulletimpacts.integer)
953 if (cl_particles_quake.integer)
954 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
957 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
958 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
959 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);
963 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
964 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
966 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
968 if (cl_particles_bulletimpacts.integer)
970 if (cl_particles_quake.integer)
971 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
974 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
975 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
976 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);
980 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
981 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
982 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);
984 else if (effectnameindex == EFFECT_TE_EXPLOSION)
986 CL_ParticleExplosion(center);
987 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);
989 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
991 CL_ParticleExplosion(center);
992 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);
994 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
996 if (cl_particles_quake.integer)
999 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
1002 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);
1004 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);
1008 CL_ParticleExplosion(center);
1009 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);
1011 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
1012 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);
1013 else if (effectnameindex == EFFECT_TE_FLAMEJET)
1015 count *= cl_particles_quality.value;
1017 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);
1019 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
1021 float i, j, inc, vel;
1024 inc = 8 / cl_particles_quality.value;
1025 for (i = -128;i < 128;i += inc)
1027 for (j = -128;j < 128;j += inc)
1029 dir[0] = j + lhrandom(0, inc);
1030 dir[1] = i + lhrandom(0, inc);
1032 org[0] = center[0] + dir[0];
1033 org[1] = center[1] + dir[1];
1034 org[2] = center[2] + lhrandom(0, 64);
1035 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1036 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);
1040 else if (effectnameindex == EFFECT_TE_TELEPORT)
1042 float i, j, k, inc, vel;
1045 if (cl_particles_quake.integer)
1046 inc = 4 / cl_particles_quality.value;
1048 inc = 8 / cl_particles_quality.value;
1049 for (i = -16;i < 16;i += inc)
1051 for (j = -16;j < 16;j += inc)
1053 for (k = -24;k < 32;k += inc)
1055 VectorSet(dir, i*8, j*8, k*8);
1056 VectorNormalize(dir);
1057 vel = lhrandom(50, 113);
1058 if (cl_particles_quake.integer)
1059 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);
1061 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);
1065 if (!cl_particles_quake.integer)
1066 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);
1067 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);
1069 else if (effectnameindex == EFFECT_TE_TEI_G3)
1070 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);
1071 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1073 if (cl_particles_smoke.integer)
1075 count *= 0.25f * cl_particles_quality.value;
1077 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);
1080 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1082 CL_ParticleExplosion(center);
1083 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);
1085 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1088 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1089 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1090 if (cl_particles_smoke.integer)
1091 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1092 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);
1093 if (cl_particles_sparks.integer)
1094 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1095 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);
1096 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);
1098 else if (effectnameindex == EFFECT_EF_FLAME)
1100 count *= 300 * cl_particles_quality.value;
1102 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);
1103 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);
1105 else if (effectnameindex == EFFECT_EF_STARDUST)
1107 count *= 200 * cl_particles_quality.value;
1109 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);
1110 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);
1112 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1116 int smoke, blood, bubbles, r, color;
1118 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1121 Vector4Set(light, 0, 0, 0, 0);
1123 if (effectnameindex == EFFECT_TR_ROCKET)
1124 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1125 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1127 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1128 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1130 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1132 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1133 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1137 matrix4x4_t tempmatrix;
1138 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1139 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);
1140 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1144 if (!spawnparticles)
1147 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1150 VectorSubtract(originmaxs, originmins, dir);
1151 len = VectorNormalizeLength(dir);
1154 dec = -ent->persistent.trail_time;
1155 ent->persistent.trail_time += len;
1156 if (ent->persistent.trail_time < 0.01f)
1159 // if we skip out, leave it reset
1160 ent->persistent.trail_time = 0.0f;
1165 // advance into this frame to reach the first puff location
1166 VectorMA(originmins, dec, dir, pos);
1169 smoke = cl_particles.integer && cl_particles_smoke.integer;
1170 blood = cl_particles.integer && cl_particles_blood.integer;
1171 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1172 qd = 1.0f / cl_particles_quality.value;
1179 if (effectnameindex == EFFECT_TR_BLOOD)
1181 if (cl_particles_quake.integer)
1183 color = particlepalette[67 + (rand()&3)];
1184 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);
1189 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);
1192 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1194 if (cl_particles_quake.integer)
1197 color = particlepalette[67 + (rand()&3)];
1198 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);
1203 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);
1209 if (effectnameindex == EFFECT_TR_ROCKET)
1211 if (cl_particles_quake.integer)
1214 color = particlepalette[ramp3[r]];
1215 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);
1219 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);
1220 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);
1223 else if (effectnameindex == EFFECT_TR_GRENADE)
1225 if (cl_particles_quake.integer)
1228 color = particlepalette[ramp3[r]];
1229 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);
1233 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);
1236 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1238 if (cl_particles_quake.integer)
1241 color = particlepalette[52 + (rand()&7)];
1242 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);
1243 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);
1245 else if (gamemode == GAME_GOODVSBAD2)
1248 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);
1252 color = particlepalette[20 + (rand()&7)];
1253 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);
1256 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1258 if (cl_particles_quake.integer)
1261 color = particlepalette[230 + (rand()&7)];
1262 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);
1263 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);
1267 color = particlepalette[226 + (rand()&7)];
1268 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);
1271 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1273 if (cl_particles_quake.integer)
1275 color = particlepalette[152 + (rand()&3)];
1276 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);
1278 else if (gamemode == GAME_GOODVSBAD2)
1281 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);
1283 else if (gamemode == GAME_PRYDON)
1286 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);
1289 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);
1291 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1294 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);
1296 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1299 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);
1301 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1302 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);
1306 if (effectnameindex == EFFECT_TR_ROCKET)
1307 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);
1308 else if (effectnameindex == EFFECT_TR_GRENADE)
1309 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);
1311 // advance to next time and position
1314 VectorMA (pos, dec, dir, pos);
1317 ent->persistent.trail_time = len;
1320 Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1323 // this is also called on point effects with spawndlight = true and
1324 // spawnparticles = true
1325 // it is called CL_ParticleTrail because most code does not want to supply
1326 // these parameters, only trail handling does
1327 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)
1329 qboolean found = false;
1330 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1332 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1333 return; // no such effect
1335 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1337 int effectinfoindex;
1340 particleeffectinfo_t *info;
1347 qboolean underwater;
1348 qboolean immediatebloodstain;
1350 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1351 VectorLerp(originmins, 0.5, originmaxs, center);
1352 supercontents = CL_PointSuperContents(center);
1353 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1354 VectorSubtract(originmaxs, originmins, traildir);
1355 traillen = VectorLength(traildir);
1356 VectorNormalize(traildir);
1357 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1359 if (info->effectnameindex == effectnameindex)
1362 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1364 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1367 // spawn a dlight if requested
1368 if (info->lightradiusstart > 0 && spawndlight)
1370 matrix4x4_t tempmatrix;
1371 if (info->trailspacing > 0)
1372 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1374 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1375 if (info->lighttime > 0 && info->lightradiusfade > 0)
1377 // light flash (explosion, etc)
1378 // called when effect starts
1379 CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0], info->lightcolor[1], info->lightcolor[2], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1384 // called by CL_LinkNetworkEntity
1385 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1386 R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, info->lightcolor, -1, info->lightcubemapnum > 0 ? va("cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
1387 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++;
1391 if (!spawnparticles)
1396 if (info->tex[1] > info->tex[0])
1398 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1399 tex = min(tex, info->tex[1] - 1);
1401 if(info->staintex[0] < 0)
1402 staintex = info->staintex[0];
1405 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1406 staintex = min(staintex, info->staintex[1] - 1);
1408 if (info->particletype == pt_decal)
1409 CL_SpawnDecalParticleForPoint(center, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1]), tex, info->color[0], info->color[1]);
1410 else if (info->orientation == PARTICLE_HBEAM)
1411 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]));
1414 if (!cl_particles.integer)
1416 switch (info->particletype)
1418 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1419 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1420 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1421 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1422 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1423 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1426 VectorCopy(originmins, trailpos);
1427 if (info->trailspacing > 0)
1429 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1430 trailstep = info->trailspacing / cl_particles_quality.value;
1431 immediatebloodstain = false;
1435 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1437 immediatebloodstain = info->particletype == pt_blood || staintex;
1439 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1440 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1442 if (info->tex[1] > info->tex[0])
1444 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1445 tex = min(tex, info->tex[1] - 1);
1449 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1450 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1451 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1454 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]));
1455 if (immediatebloodstain && part)
1457 immediatebloodstain = false;
1458 CL_ImmediateBloodStain(part);
1461 VectorMA(trailpos, trailstep, traildir, trailpos);
1468 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1471 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)
1473 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1481 void CL_EntityParticles (const entity_t *ent)
1484 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1485 static vec3_t avelocities[NUMVERTEXNORMALS];
1486 if (!cl_particles.integer) return;
1487 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1489 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1491 if (!avelocities[0][0])
1492 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1493 avelocities[0][i] = lhrandom(0, 2.55);
1495 for (i = 0;i < NUMVERTEXNORMALS;i++)
1497 yaw = cl.time * avelocities[i][0];
1498 pitch = cl.time * avelocities[i][1];
1499 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1500 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1501 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1502 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);
1507 void CL_ReadPointFile_f (void)
1509 vec3_t org, leakorg;
1511 char *pointfile = NULL, *pointfilepos, *t, tchar;
1512 char name[MAX_OSPATH];
1517 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1518 strlcat (name, ".pts", sizeof (name));
1519 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1522 Con_Printf("Could not open %s\n", name);
1526 Con_Printf("Reading %s...\n", name);
1527 VectorClear(leakorg);
1530 pointfilepos = pointfile;
1531 while (*pointfilepos)
1533 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1538 while (*t && *t != '\n' && *t != '\r')
1542 #if _MSC_VER >= 1400
1543 #define sscanf sscanf_s
1545 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1551 VectorCopy(org, leakorg);
1554 if (cl.num_particles < cl.max_particles - 3)
1557 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);
1560 Mem_Free(pointfile);
1561 VectorCopy(leakorg, org);
1562 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1564 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);
1565 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);
1566 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);
1571 CL_ParseParticleEffect
1573 Parse an effect out of the server message
1576 void CL_ParseParticleEffect (void)
1579 int i, count, msgcount, color;
1581 MSG_ReadVector(org, cls.protocol);
1582 for (i=0 ; i<3 ; i++)
1583 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1584 msgcount = MSG_ReadByte ();
1585 color = MSG_ReadByte ();
1587 if (msgcount == 255)
1592 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1597 CL_ParticleExplosion
1601 void CL_ParticleExplosion (const vec3_t org)
1607 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1608 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1610 if (cl_particles_quake.integer)
1612 for (i = 0;i < 1024;i++)
1618 color = particlepalette[ramp1[r]];
1619 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);
1623 color = particlepalette[ramp2[r]];
1624 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);
1630 i = CL_PointSuperContents(org);
1631 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1633 if (cl_particles.integer && cl_particles_bubbles.integer)
1634 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1635 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);
1639 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1641 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1645 for (k = 0;k < 16;k++)
1648 VectorMA(org, 128, v2, v);
1649 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1650 if (trace.fraction >= 0.1)
1653 VectorSubtract(trace.endpos, org, v2);
1654 VectorScale(v2, 2.0f, v2);
1655 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);
1661 if (cl_particles_explosions_shell.integer)
1662 R_NewExplosion(org);
1667 CL_ParticleExplosion2
1671 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1674 if (!cl_particles.integer) return;
1676 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1678 k = particlepalette[colorStart + (i % colorLength)];
1679 if (cl_particles_quake.integer)
1680 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);
1682 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);
1686 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1689 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1690 if (cl_particles_sparks.integer)
1692 sparkcount *= cl_particles_quality.value;
1693 while(sparkcount-- > 0)
1694 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);
1698 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1701 VectorMAM(0.5f, originmins, 0.5f, originmaxs, center);
1702 if (cl_particles_smoke.integer)
1704 smokecount *= cl_particles_quality.value;
1705 while(smokecount-- > 0)
1706 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);
1710 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)
1714 if (!cl_particles.integer) return;
1715 VectorMAM(0.5f, mins, 0.5f, maxs, center);
1717 count = (int)(count * cl_particles_quality.value);
1720 k = particlepalette[colorbase + (rand()&3)];
1721 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);
1725 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1728 float minz, maxz, lifetime = 30;
1730 if (!cl_particles.integer) return;
1731 if (dir[2] < 0) // falling
1733 minz = maxs[2] + dir[2] * 0.1;
1736 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1741 maxz = maxs[2] + dir[2] * 0.1;
1743 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1746 count = (int)(count * cl_particles_quality.value);
1751 if (!cl_particles_rain.integer) break;
1752 count *= 4; // ick, this should be in the mod or maps?
1756 k = particlepalette[colorbase + (rand()&3)];
1757 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1758 if (gamemode == GAME_GOODVSBAD2)
1759 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);
1761 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);
1765 if (!cl_particles_snow.integer) break;
1768 k = particlepalette[colorbase + (rand()&3)];
1769 VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz));
1770 if (gamemode == GAME_GOODVSBAD2)
1771 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);
1773 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);
1777 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1781 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1782 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1783 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1784 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1786 #define PARTICLETEXTURESIZE 64
1787 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1789 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1793 dz = 1 - (dx*dx+dy*dy);
1794 if (dz > 0) // it does hit the sphere
1798 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1799 VectorNormalize(normal);
1800 dot = DotProduct(normal, light);
1801 if (dot > 0.5) // interior reflection
1802 f += ((dot * 2) - 1);
1803 else if (dot < -0.5) // exterior reflection
1804 f += ((dot * -2) - 1);
1806 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1807 VectorNormalize(normal);
1808 dot = DotProduct(normal, light);
1809 if (dot > 0.5) // interior reflection
1810 f += ((dot * 2) - 1);
1811 else if (dot < -0.5) // exterior reflection
1812 f += ((dot * -2) - 1);
1814 f += 16; // just to give it a haze so you can see the outline
1815 f = bound(0, f, 255);
1816 return (unsigned char) f;
1822 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1823 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1825 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1826 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1827 *width = particlefontcellwidth;
1828 *height = particlefontcellheight;
1831 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1833 int basex, basey, w, h, y;
1834 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1835 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1836 Sys_Error("invalid particle texture size for autogenerating");
1837 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1838 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1841 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1844 float cx, cy, dx, dy, f, iradius;
1846 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1847 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1848 iradius = 1.0f / radius;
1849 alpha *= (1.0f / 255.0f);
1850 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1852 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1856 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1861 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1862 d[0] += (int)(f * (blue - d[0]));
1863 d[1] += (int)(f * (green - d[1]));
1864 d[2] += (int)(f * (red - d[2]));
1870 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1873 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1875 data[0] = bound(minb, data[0], maxb);
1876 data[1] = bound(ming, data[1], maxg);
1877 data[2] = bound(minr, data[2], maxr);
1881 void particletextureinvert(unsigned char *data)
1884 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1886 data[0] = 255 - data[0];
1887 data[1] = 255 - data[1];
1888 data[2] = 255 - data[2];
1892 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1893 static void R_InitBloodTextures (unsigned char *particletexturedata)
1896 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1897 unsigned char *data = Mem_Alloc(tempmempool, datasize);
1900 for (i = 0;i < 8;i++)
1902 memset(data, 255, datasize);
1903 for (k = 0;k < 24;k++)
1904 particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1905 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1906 particletextureinvert(data);
1907 setuptex(tex_bloodparticle[i], data, particletexturedata);
1911 for (i = 0;i < 8;i++)
1913 memset(data, 255, datasize);
1915 for (j = 1;j < 10;j++)
1916 for (k = min(j, m - 1);k < m;k++)
1917 particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1918 //particletextureclamp(data, 32, 32, 32, 255, 255, 255);
1919 particletextureinvert(data);
1920 setuptex(tex_blooddecal[i], data, particletexturedata);
1926 //uncomment this to make engine save out particle font to a tga file when run
1927 //#define DUMPPARTICLEFONT
1929 static void R_InitParticleTexture (void)
1931 int x, y, d, i, k, m;
1932 int basex, basey, w, h;
1933 float dx, dy, f, s1, t1, s2, t2;
1936 fs_offset_t filesize;
1937 char texturename[MAX_QPATH];
1939 // a note: decals need to modulate (multiply) the background color to
1940 // properly darken it (stain), and they need to be able to alpha fade,
1941 // this is a very difficult challenge because it means fading to white
1942 // (no change to background) rather than black (darkening everything
1943 // behind the whole decal polygon), and to accomplish this the texture is
1944 // inverted (dark red blood on white background becomes brilliant cyan
1945 // and white on black background) so we can alpha fade it to black, then
1946 // we invert it again during the blendfunc to make it work...
1948 #ifndef DUMPPARTICLEFONT
1949 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR, false);
1952 particlefonttexture = decalskinframe->base;
1953 // TODO maybe allow custom grid size?
1954 particlefontwidth = image_width;
1955 particlefontheight = image_height;
1956 particlefontcellwidth = image_width / 8;
1957 particlefontcellheight = image_height / 8;
1958 particlefontcols = 8;
1959 particlefontrows = 8;
1964 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1965 size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4;
1966 unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize);
1967 unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1968 unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2);
1970 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1971 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1972 particlefontcols = 8;
1973 particlefontrows = 8;
1975 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1978 for (i = 0;i < 8;i++)
1980 memset(data, 255, datasize);
1983 fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1984 fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1986 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1988 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1989 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1991 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1992 d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192;
1994 d = (int)(d * (1-(dx*dx+dy*dy)));
1995 d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7;
1996 d = bound(0, d, 255);
1997 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2004 setuptex(tex_smoke[i], data, particletexturedata);
2008 memset(data, 255, datasize);
2009 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2011 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2012 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2014 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2015 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
2016 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f));
2019 setuptex(tex_rainsplash, data, particletexturedata);
2022 memset(data, 255, datasize);
2023 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2025 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2026 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2028 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2029 d = (int)(256 * (1 - (dx*dx+dy*dy)));
2030 d = bound(0, d, 255);
2031 data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d;
2034 setuptex(tex_particle, data, particletexturedata);
2037 memset(data, 255, datasize);
2038 light[0] = 1;light[1] = 1;light[2] = 1;
2039 VectorNormalize(light);
2040 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2042 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2043 // stretch upper half of bubble by +50% and shrink lower half by -50%
2044 // (this gives an elongated teardrop shape)
2046 dy = (dy - 0.5f) * 2.0f;
2048 dy = (dy - 0.5f) / 1.5f;
2049 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2051 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2052 // shrink bubble width to half
2054 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2057 setuptex(tex_raindrop, data, particletexturedata);
2060 memset(data, 255, datasize);
2061 light[0] = 1;light[1] = 1;light[2] = 1;
2062 VectorNormalize(light);
2063 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2065 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2066 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2068 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2069 data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light);
2072 setuptex(tex_bubble, data, particletexturedata);
2074 // Blood particles and blood decals
2075 R_InitBloodTextures (particletexturedata);
2078 for (i = 0;i < 8;i++)
2080 memset(data, 255, datasize);
2081 for (k = 0;k < 12;k++)
2082 particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2083 for (k = 0;k < 3;k++)
2084 particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2085 //particletextureclamp(data, 64, 64, 64, 255, 255, 255);
2086 particletextureinvert(data);
2087 setuptex(tex_bulletdecal[i], data, particletexturedata);
2090 #ifdef DUMPPARTICLEFONT
2091 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2094 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2095 particlefonttexture = decalskinframe->base;
2097 Mem_Free(particletexturedata);
2102 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2104 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2105 particletexture[i].texture = particlefonttexture;
2106 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2107 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2108 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2109 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2112 #ifndef DUMPPARTICLEFONT
2113 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR, true, r_texture_convertsRGB_particles.integer);
2114 if (!particletexture[tex_beam].texture)
2117 unsigned char noise3[64][64], data2[64][16][4];
2119 fractalnoise(&noise3[0][0], 64, 4);
2121 for (y = 0;y < 64;y++)
2123 dy = (y - 0.5f*64) / (64*0.5f-1);
2124 for (x = 0;x < 16;x++)
2126 dx = (x - 0.5f*16) / (16*0.5f-2);
2127 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2128 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2129 data2[y][x][3] = 255;
2133 #ifdef DUMPPARTICLEFONT
2134 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2136 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR, NULL);
2138 particletexture[tex_beam].s1 = 0;
2139 particletexture[tex_beam].t1 = 0;
2140 particletexture[tex_beam].s2 = 1;
2141 particletexture[tex_beam].t2 = 1;
2143 // now load an texcoord/texture override file
2144 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2151 if(!COM_ParseToken_Simple(&bufptr, true, false))
2153 if(!strcmp(com_token, "\n"))
2154 continue; // empty line
2155 i = atoi(com_token);
2163 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2165 s1 = atof(com_token);
2166 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2168 t1 = atof(com_token);
2169 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2171 s2 = atof(com_token);
2172 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2174 t2 = atof(com_token);
2175 strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename));
2176 if (COM_ParseToken_Simple(&bufptr, true, false) && strcmp(com_token, "\n"))
2177 strlcpy(texturename, com_token, sizeof(texturename));
2184 strlcpy(texturename, com_token, sizeof(texturename));
2187 if (!texturename[0])
2189 Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n");
2192 if (i < 0 || i >= MAX_PARTICLETEXTURES)
2194 Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES);
2197 particletexture[i].texture = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR, false)->base;
2198 particletexture[i].s1 = s1;
2199 particletexture[i].t1 = t1;
2200 particletexture[i].s2 = s2;
2201 particletexture[i].t2 = t2;
2207 static void r_part_start(void)
2210 // generate particlepalette for convenience from the main one
2211 for (i = 0;i < 256;i++)
2212 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2213 particletexturepool = R_AllocTexturePool();
2214 R_InitParticleTexture ();
2215 CL_Particles_LoadEffectInfo();
2218 static void r_part_shutdown(void)
2220 R_FreeTexturePool(&particletexturepool);
2223 static void r_part_newmap(void)
2226 R_SkinFrame_MarkUsed(decalskinframe);
2227 CL_Particles_LoadEffectInfo();
2230 #define BATCHSIZE 256
2231 unsigned short particle_elements[BATCHSIZE*6];
2232 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2234 void R_Particles_Init (void)
2237 for (i = 0;i < BATCHSIZE;i++)
2239 particle_elements[i*6+0] = i*4+0;
2240 particle_elements[i*6+1] = i*4+1;
2241 particle_elements[i*6+2] = i*4+2;
2242 particle_elements[i*6+3] = i*4+0;
2243 particle_elements[i*6+4] = i*4+2;
2244 particle_elements[i*6+5] = i*4+3;
2247 Cvar_RegisterVariable(&r_drawparticles);
2248 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2249 Cvar_RegisterVariable(&r_drawdecals);
2250 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2251 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2254 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2256 int surfacelistindex;
2258 float *v3f, *t2f, *c4f;
2259 particletexture_t *tex;
2260 float right[3], up[3], size, ca;
2261 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value;
2262 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2264 RSurf_ActiveWorldEntity();
2266 r_refdef.stats.drawndecals += numsurfaces;
2267 R_Mesh_ResetTextureState();
2268 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2269 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2270 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2271 GL_DepthMask(false);
2272 GL_DepthRange(0, 1);
2273 GL_PolygonOffset(0, 0);
2275 GL_CullFace(GL_NONE);
2277 // generate all the vertices at once
2278 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2280 d = cl.decals + surfacelist[surfacelistindex];
2283 c4f = particle_color4f + 16*surfacelistindex;
2284 ca = d->alpha * alphascale;
2285 // ensure alpha multiplier saturates properly
2286 if (ca > 1.0f / 256.0f)
2288 if (r_refdef.fogenabled)
2289 ca *= RSurf_FogVertex(d->org);
2290 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2291 Vector4Copy(c4f, c4f + 4);
2292 Vector4Copy(c4f, c4f + 8);
2293 Vector4Copy(c4f, c4f + 12);
2295 // calculate vertex positions
2296 size = d->size * cl_particles_size.value;
2297 VectorVectors(d->normal, right, up);
2298 VectorScale(right, size, right);
2299 VectorScale(up, size, up);
2300 v3f = particle_vertex3f + 12*surfacelistindex;
2301 v3f[ 0] = d->org[0] - right[0] - up[0];
2302 v3f[ 1] = d->org[1] - right[1] - up[1];
2303 v3f[ 2] = d->org[2] - right[2] - up[2];
2304 v3f[ 3] = d->org[0] - right[0] + up[0];
2305 v3f[ 4] = d->org[1] - right[1] + up[1];
2306 v3f[ 5] = d->org[2] - right[2] + up[2];
2307 v3f[ 6] = d->org[0] + right[0] + up[0];
2308 v3f[ 7] = d->org[1] + right[1] + up[1];
2309 v3f[ 8] = d->org[2] + right[2] + up[2];
2310 v3f[ 9] = d->org[0] + right[0] - up[0];
2311 v3f[10] = d->org[1] + right[1] - up[1];
2312 v3f[11] = d->org[2] + right[2] - up[2];
2314 // calculate texcoords
2315 tex = &particletexture[d->texnum];
2316 t2f = particle_texcoord2f + 8*surfacelistindex;
2317 t2f[0] = tex->s1;t2f[1] = tex->t2;
2318 t2f[2] = tex->s1;t2f[3] = tex->t1;
2319 t2f[4] = tex->s2;t2f[5] = tex->t1;
2320 t2f[6] = tex->s2;t2f[7] = tex->t2;
2323 // now render the decals all at once
2324 // (this assumes they all use one particle font texture!)
2325 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2326 R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1);
2327 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2330 void R_DrawDecals (void)
2333 int drawdecals = r_drawdecals.integer;
2338 int killsequence = cl.decalsequence - max(0, cl_decals_max.integer);
2340 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2341 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2343 // LordHavoc: early out conditions
2347 decalfade = frametime * 256 / cl_decals_fadetime.value;
2348 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2349 drawdist2 = drawdist2*drawdist2;
2351 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2353 if (!decal->typeindex)
2356 if (killsequence - decal->decalsequence > 0)
2359 if (cl.time > decal->time2 + cl_decals_time.value)
2361 decal->alpha -= decalfade;
2362 if (decal->alpha <= 0)
2368 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2370 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2371 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2377 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2383 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))
2384 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2387 decal->typeindex = 0;
2388 if (cl.free_decal > i)
2392 // reduce cl.num_decals if possible
2393 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2396 if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS)
2398 decal_t *olddecals = cl.decals;
2399 cl.max_decals = min(cl.max_decals * 2, MAX_DECALS);
2400 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2401 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2402 Mem_Free(olddecals);
2405 r_refdef.stats.totaldecals = cl.num_decals;
2408 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2410 int surfacelistindex;
2411 int batchstart, batchcount;
2412 const particle_t *p;
2414 rtexture_t *texture;
2415 float *v3f, *t2f, *c4f;
2416 particletexture_t *tex;
2417 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha;
2418 float ambient[3], diffuse[3], diffusenormal[3];
2419 vec4_t colormultiplier;
2421 RSurf_ActiveWorldEntity();
2423 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));
2425 r_refdef.stats.particles += numsurfaces;
2426 R_Mesh_ResetTextureState();
2427 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2428 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2429 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2430 GL_DepthMask(false);
2431 GL_DepthRange(0, 1);
2432 GL_PolygonOffset(0, 0);
2434 GL_AlphaTest(false);
2435 GL_CullFace(GL_NONE);
2437 // first generate all the vertices at once
2438 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2440 p = cl.particles + surfacelist[surfacelistindex];
2442 blendmode = (pblend_t)p->blendmode;
2446 case PBLEND_INVALID:
2448 alpha = p->alpha * colormultiplier[3];
2449 // ensure alpha multiplier saturates properly
2452 // additive and modulate can just fade out in fog (this is correct)
2453 if (r_refdef.fogenabled)
2454 alpha *= RSurf_FogVertex(p->org);
2455 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2456 alpha *= 1.0f / 256.0f;
2457 c4f[0] = p->color[0] * alpha;
2458 c4f[1] = p->color[1] * alpha;
2459 c4f[2] = p->color[2] * alpha;
2463 alpha = p->alpha * colormultiplier[3];
2464 // ensure alpha multiplier saturates properly
2467 // additive and modulate can just fade out in fog (this is correct)
2468 if (r_refdef.fogenabled)
2469 alpha *= RSurf_FogVertex(p->org);
2470 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2471 c4f[0] = p->color[0] * colormultiplier[0] * alpha;
2472 c4f[1] = p->color[1] * colormultiplier[1] * alpha;
2473 c4f[2] = p->color[2] * colormultiplier[2] * alpha;
2477 c4f[0] = p->color[0] * colormultiplier[0];
2478 c4f[1] = p->color[1] * colormultiplier[1];
2479 c4f[2] = p->color[2] * colormultiplier[2];
2480 c4f[3] = p->alpha * colormultiplier[3];
2481 // note: lighting is not cheap!
2482 if (particletype[p->typeindex].lighting)
2484 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2485 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2486 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2487 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2489 // mix in the fog color
2490 if (r_refdef.fogenabled)
2492 fog = RSurf_FogVertex(p->org);
2494 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2495 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2496 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2500 // copy the color into the other three vertices
2501 Vector4Copy(c4f, c4f + 4);
2502 Vector4Copy(c4f, c4f + 8);
2503 Vector4Copy(c4f, c4f + 12);
2505 size = p->size * cl_particles_size.value;
2506 tex = &particletexture[p->texnum];
2507 switch(p->orientation)
2509 // case PARTICLE_INVALID:
2510 case PARTICLE_BILLBOARD:
2511 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2512 VectorScale(r_refdef.view.up, size, up);
2513 v3f[ 0] = p->org[0] - right[0] - up[0];
2514 v3f[ 1] = p->org[1] - right[1] - up[1];
2515 v3f[ 2] = p->org[2] - right[2] - up[2];
2516 v3f[ 3] = p->org[0] - right[0] + up[0];
2517 v3f[ 4] = p->org[1] - right[1] + up[1];
2518 v3f[ 5] = p->org[2] - right[2] + up[2];
2519 v3f[ 6] = p->org[0] + right[0] + up[0];
2520 v3f[ 7] = p->org[1] + right[1] + up[1];
2521 v3f[ 8] = p->org[2] + right[2] + up[2];
2522 v3f[ 9] = p->org[0] + right[0] - up[0];
2523 v3f[10] = p->org[1] + right[1] - up[1];
2524 v3f[11] = p->org[2] + right[2] - up[2];
2525 t2f[0] = tex->s1;t2f[1] = tex->t2;
2526 t2f[2] = tex->s1;t2f[3] = tex->t1;
2527 t2f[4] = tex->s2;t2f[5] = tex->t1;
2528 t2f[6] = tex->s2;t2f[7] = tex->t2;
2530 case PARTICLE_ORIENTED_DOUBLESIDED:
2531 VectorVectors(p->vel, right, up);
2532 VectorScale(right, size * p->stretch, right);
2533 VectorScale(up, size, up);
2534 v3f[ 0] = p->org[0] - right[0] - up[0];
2535 v3f[ 1] = p->org[1] - right[1] - up[1];
2536 v3f[ 2] = p->org[2] - right[2] - up[2];
2537 v3f[ 3] = p->org[0] - right[0] + up[0];
2538 v3f[ 4] = p->org[1] - right[1] + up[1];
2539 v3f[ 5] = p->org[2] - right[2] + up[2];
2540 v3f[ 6] = p->org[0] + right[0] + up[0];
2541 v3f[ 7] = p->org[1] + right[1] + up[1];
2542 v3f[ 8] = p->org[2] + right[2] + up[2];
2543 v3f[ 9] = p->org[0] + right[0] - up[0];
2544 v3f[10] = p->org[1] + right[1] - up[1];
2545 v3f[11] = p->org[2] + right[2] - up[2];
2546 t2f[0] = tex->s1;t2f[1] = tex->t2;
2547 t2f[2] = tex->s1;t2f[3] = tex->t1;
2548 t2f[4] = tex->s2;t2f[5] = tex->t1;
2549 t2f[6] = tex->s2;t2f[7] = tex->t2;
2551 case PARTICLE_SPARK:
2552 len = VectorLength(p->vel);
2553 VectorNormalize2(p->vel, up);
2554 lenfactor = p->stretch * 0.04 * len;
2555 if(lenfactor < size * 0.5)
2556 lenfactor = size * 0.5;
2557 VectorMA(p->org, -lenfactor, up, v);
2558 VectorMA(p->org, lenfactor, up, up2);
2559 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2560 t2f[0] = tex->s1;t2f[1] = tex->t2;
2561 t2f[2] = tex->s1;t2f[3] = tex->t1;
2562 t2f[4] = tex->s2;t2f[5] = tex->t1;
2563 t2f[6] = tex->s2;t2f[7] = tex->t2;
2565 case PARTICLE_VBEAM:
2566 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2567 VectorSubtract(p->vel, p->org, up);
2568 VectorNormalize(up);
2569 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2570 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2571 t2f[0] = tex->s2;t2f[1] = v[0];
2572 t2f[2] = tex->s1;t2f[3] = v[0];
2573 t2f[4] = tex->s1;t2f[5] = v[1];
2574 t2f[6] = tex->s2;t2f[7] = v[1];
2576 case PARTICLE_HBEAM:
2577 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2578 VectorSubtract(p->vel, p->org, up);
2579 VectorNormalize(up);
2580 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2581 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2582 t2f[0] = v[0];t2f[1] = tex->t1;
2583 t2f[2] = v[0];t2f[3] = tex->t2;
2584 t2f[4] = v[1];t2f[5] = tex->t2;
2585 t2f[6] = v[1];t2f[7] = tex->t1;
2590 // now render batches of particles based on blendmode and texture
2591 blendmode = PBLEND_INVALID;
2595 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2597 p = cl.particles + surfacelist[surfacelistindex];
2599 if (blendmode != p->blendmode)
2601 blendmode = (pblend_t)p->blendmode;
2605 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2607 case PBLEND_INVALID:
2609 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2612 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2616 if (texture != particletexture[p->texnum].texture)
2618 texture = particletexture[p->texnum].texture;
2619 R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1);
2622 // iterate until we find a change in settings
2623 batchstart = surfacelistindex++;
2624 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2626 p = cl.particles + surfacelist[surfacelistindex];
2627 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2631 batchcount = surfacelistindex - batchstart;
2632 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2636 void R_DrawParticles (void)
2639 int drawparticles = r_drawparticles.integer;
2640 float minparticledist;
2642 float gravity, frametime, f, dist, oldorg[3];
2648 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2649 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2651 // LordHavoc: early out conditions
2652 if (!cl.num_particles)
2655 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2656 gravity = frametime * cl.movevars_gravity;
2657 update = frametime > 0;
2658 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2659 drawdist2 = drawdist2*drawdist2;
2661 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2665 if (cl.free_particle > i)
2666 cl.free_particle = i;
2672 if (p->delayedspawn > cl.time)
2674 p->delayedspawn = 0;
2676 p->size += p->sizeincrease * frametime;
2677 p->alpha -= p->alphafade * frametime;
2679 if (p->alpha <= 0 || p->die <= cl.time)
2682 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2684 if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2686 if (p->typeindex == pt_blood)
2687 p->size += frametime * 8;
2689 p->vel[2] -= p->gravity * gravity;
2690 f = 1.0f - min(p->liquidfriction * frametime, 1);
2691 VectorScale(p->vel, f, p->vel);
2695 p->vel[2] -= p->gravity * gravity;
2698 f = 1.0f - min(p->airfriction * frametime, 1);
2699 VectorScale(p->vel, f, p->vel);
2703 VectorCopy(p->org, oldorg);
2704 VectorMA(p->org, frametime, p->vel, p->org);
2705 // if (p->bounce && cl.time >= p->delayedcollisions)
2706 if (p->bounce && cl_particles_collisions.integer)
2708 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);
2709 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2710 // or if the trace hit something flagged as NOIMPACT
2711 // then remove the particle
2712 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2714 VectorCopy(trace.endpos, p->org);
2715 // react if the particle hit something
2716 if (trace.fraction < 1)
2718 VectorCopy(trace.endpos, p->org);
2720 if (p->staintexnum >= 0)
2722 // blood - splash on solid
2723 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2726 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)),
2727 p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)));
2728 if (cl_decals.integer)
2730 // create a decal for the blood splat
2731 a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]);
2732 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals!
2737 if (p->typeindex == pt_blood)
2739 // blood - splash on solid
2740 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2742 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2744 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)));
2745 if (cl_decals.integer)
2747 // create a decal for the blood splat
2748 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);
2753 else if (p->bounce < 0)
2755 // bounce -1 means remove on impact
2760 // anything else - bounce off solid
2761 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2762 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2763 if (DotProduct(p->vel, p->vel) < 0.03)
2764 VectorClear(p->vel);
2770 if (p->typeindex != pt_static)
2772 switch (p->typeindex)
2774 case pt_entityparticle:
2775 // particle that removes itself after one rendered frame
2782 a = CL_PointSuperContents(p->org);
2783 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2787 a = CL_PointSuperContents(p->org);
2788 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2792 a = CL_PointSuperContents(p->org);
2793 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2797 if (cl.time > p->time2)
2800 p->time2 = cl.time + (rand() & 3) * 0.1;
2801 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2802 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2804 a = CL_PointSuperContents(p->org);
2805 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2813 else if (p->delayedspawn)
2817 // don't render particles too close to the view (they chew fillrate)
2818 // also don't render particles behind the view (useless)
2819 // further checks to cull to the frustum would be too slow here
2820 switch(p->typeindex)
2823 // beams have no culling
2824 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2827 if(cl_particles_visculling.integer)
2828 if (!r_refdef.viewcache.world_novis)
2829 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2831 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2833 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2836 // anything else just has to be in front of the viewer and visible at this distance
2837 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2838 R_MeshQueue_AddTransparent(p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2845 if (cl.free_particle > i)
2846 cl.free_particle = i;
2849 // reduce cl.num_particles if possible
2850 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2853 if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES)
2855 particle_t *oldparticles = cl.particles;
2856 cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES);
2857 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2858 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2859 Mem_Free(oldparticles);