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 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
121 particleeffectinfo_t;
123 #define MAX_PARTICLEEFFECTNAME 256
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 #define MAX_PARTICLEEFFECTINFO 4096
128 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
130 static int particlepalette[256];
132 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
133 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
134 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
135 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
136 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
137 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
138 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
139 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
140 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
141 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
142 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
143 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
144 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
145 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
146 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
147 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
148 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
149 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
150 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
151 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
152 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
153 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
154 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
155 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
156 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
157 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
158 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
159 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
160 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
161 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
162 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
163 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
166 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
167 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
168 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
170 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
172 #define MAX_PARTICLETEXTURES 1024
173 // particletexture_t is a rectangle in the particlefonttexture
174 typedef struct particletexture_s
177 float s1, t1, s2, t2;
181 static rtexturepool_t *particletexturepool;
182 static rtexture_t *particlefonttexture;
183 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
184 skinframe_t *decalskinframe;
186 // texture numbers in particle font
187 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
188 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
189 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
190 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
191 static const int tex_rainsplash = 32;
192 static const int tex_particle = 63;
193 static const int tex_bubble = 62;
194 static const int tex_raindrop = 61;
195 static const int tex_beam = 60;
197 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
198 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
199 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
200 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
201 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
202 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
203 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
204 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
205 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
206 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
207 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
208 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
209 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
210 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
211 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
212 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
213 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
214 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
215 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
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_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
224 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
230 particleeffectinfo_t *info = NULL;
231 const char *text = textstart;
233 effectinfoindex = -1;
234 for (linenumber = 1;;linenumber++)
237 for (arrayindex = 0;arrayindex < 16;arrayindex++)
238 argv[arrayindex][0] = 0;
241 if (!COM_ParseToken_Simple(&text, true, false))
243 if (!strcmp(com_token, "\n"))
247 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
253 #define checkparms(n) if (argc != (n)) {Con_Printf("effectinfo.txt:%i: error while parsing: %s given %i parameters, should be %i parameters\n", linenumber, argv[0], argc, (n));break;}
254 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
255 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
256 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
257 #define readfloat(var) checkparms(2);var = atof(argv[1])
258 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
259 if (!strcmp(argv[0], "effect"))
264 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
266 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
269 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
271 if (particleeffectname[effectnameindex][0])
273 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
278 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
282 // if we run out of names, abort
283 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
285 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
288 info = particleeffectinfo + effectinfoindex;
289 info->effectnameindex = effectnameindex;
290 info->particletype = pt_alphastatic;
291 info->blendmode = particletype[info->particletype].blendmode;
292 info->orientation = particletype[info->particletype].orientation;
293 info->tex[0] = tex_particle;
294 info->tex[1] = tex_particle;
295 info->color[0] = 0xFFFFFF;
296 info->color[1] = 0xFFFFFF;
300 info->alpha[1] = 256;
301 info->alpha[2] = 256;
302 info->time[0] = 9999;
303 info->time[1] = 9999;
304 VectorSet(info->lightcolor, 1, 1, 1);
305 info->lightshadow = true;
306 info->lighttime = 9999;
307 info->stretchfactor = 1;
308 info->staincolor[0] = (unsigned int)-1;
309 info->staincolor[1] = (unsigned int)-1;
310 info->staintex[0] = -1;
311 info->staintex[1] = -1;
313 else if (info == NULL)
315 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
318 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
319 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
320 else if (!strcmp(argv[0], "type"))
323 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
324 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
325 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
326 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
327 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
328 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
329 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
330 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
331 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
332 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
333 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
334 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
335 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
336 info->blendmode = particletype[info->particletype].blendmode;
337 info->orientation = particletype[info->particletype].orientation;
339 else if (!strcmp(argv[0], "blend"))
342 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
343 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
344 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
345 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
347 else if (!strcmp(argv[0], "orientation"))
350 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
351 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
352 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
353 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
354 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
356 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
357 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
358 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
359 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
360 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
361 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
362 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
363 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
364 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
365 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
366 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
367 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
368 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
369 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
370 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
371 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
372 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
373 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
374 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
375 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
376 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
377 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
378 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
379 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
380 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
381 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
382 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
383 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
385 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
394 int CL_ParticleEffectIndexForName(const char *name)
397 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
398 if (!strcmp(particleeffectname[i], name))
403 const char *CL_ParticleEffectNameForIndex(int i)
405 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
407 return particleeffectname[i];
410 // MUST match effectnameindex_t in client.h
411 static const char *standardeffectnames[EFFECT_TOTAL] =
435 "TE_TEI_BIGEXPLOSION",
451 void CL_Particles_LoadEffectInfo(void)
454 unsigned char *filedata;
455 fs_offset_t filesize;
456 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
457 memset(particleeffectname, 0, sizeof(particleeffectname));
458 for (i = 0;i < EFFECT_TOTAL;i++)
459 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
460 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
463 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
473 void CL_ReadPointFile_f (void);
474 void CL_Particles_Init (void)
476 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)");
477 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
479 Cvar_RegisterVariable (&cl_particles);
480 Cvar_RegisterVariable (&cl_particles_quality);
481 Cvar_RegisterVariable (&cl_particles_alpha);
482 Cvar_RegisterVariable (&cl_particles_size);
483 Cvar_RegisterVariable (&cl_particles_quake);
484 Cvar_RegisterVariable (&cl_particles_blood);
485 Cvar_RegisterVariable (&cl_particles_blood_alpha);
486 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
487 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
488 Cvar_RegisterVariable (&cl_particles_explosions_shell);
489 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
490 Cvar_RegisterVariable (&cl_particles_rain);
491 Cvar_RegisterVariable (&cl_particles_snow);
492 Cvar_RegisterVariable (&cl_particles_smoke);
493 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
494 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
495 Cvar_RegisterVariable (&cl_particles_sparks);
496 Cvar_RegisterVariable (&cl_particles_bubbles);
497 Cvar_RegisterVariable (&cl_particles_visculling);
498 Cvar_RegisterVariable (&cl_decals);
499 Cvar_RegisterVariable (&cl_decals_visculling);
500 Cvar_RegisterVariable (&cl_decals_time);
501 Cvar_RegisterVariable (&cl_decals_fadetime);
502 Cvar_RegisterVariable (&cl_decals_newsystem);
503 Cvar_RegisterVariable (&cl_decals_bias);
506 void CL_Particles_Shutdown (void)
510 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
511 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
513 // list of all 26 parameters:
514 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
515 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
516 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
517 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
518 // palpha - opacity of particle as 0-255 (can be more than 255)
519 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
520 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
521 // pgravity - how much effect gravity has on the particle (0-1)
522 // 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
523 // px,py,pz - starting origin of particle
524 // pvx,pvy,pvz - starting velocity of particle
525 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
526 // blendmode - one of the PBLEND_ values
527 // orientation - one of the PARTICLE_ values
528 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
529 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
530 particle_t *CL_NewParticle(unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex)
535 if (!cl_particles.integer)
537 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
538 if (cl.free_particle >= cl.max_particles)
541 lifetime = palpha / min(1, palphafade);
542 part = &cl.particles[cl.free_particle++];
543 if (cl.num_particles < cl.free_particle)
544 cl.num_particles = cl.free_particle;
545 memset(part, 0, sizeof(*part));
546 part->typeindex = ptypeindex;
547 part->blendmode = blendmode;
548 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
550 particletexture_t *tex = &particletexture[ptex];
551 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
552 part->orientation = PARTICLE_VBEAM;
554 part->orientation = PARTICLE_HBEAM;
557 part->orientation = orientation;
558 l2 = (int)lhrandom(0.5, 256.5);
560 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
561 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
562 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
563 part->staintexnum = staintex;
564 if(staincolor1 >= 0 && staincolor2 >= 0)
566 l2 = (int)lhrandom(0.5, 256.5);
568 if(blendmode == PBLEND_INVMOD)
570 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
571 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
572 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
576 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
577 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
578 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
580 if(r > 0xFF) r = 0xFF;
581 if(g > 0xFF) g = 0xFF;
582 if(b > 0xFF) b = 0xFF;
586 r = part->color[0]; // -1 is shorthand for stain = particle color
590 part->staincolor = r * 65536 + g * 256 + b;
593 part->sizeincrease = psizeincrease;
594 part->alpha = palpha;
595 part->alphafade = palphafade;
596 part->gravity = pgravity;
597 part->bounce = pbounce;
598 part->stretch = stretch;
600 part->org[0] = px + originjitter * v[0];
601 part->org[1] = py + originjitter * v[1];
602 part->org[2] = pz + originjitter * v[2];
603 part->vel[0] = pvx + velocityjitter * v[0];
604 part->vel[1] = pvy + velocityjitter * v[1];
605 part->vel[2] = pvz + velocityjitter * v[2];
607 part->airfriction = pairfriction;
608 part->liquidfriction = pliquidfriction;
609 part->die = cl.time + lifetime;
610 part->delayedcollisions = 0;
611 part->qualityreduction = pqualityreduction;
612 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
613 if (part->typeindex == pt_rain)
617 float lifetime = part->die - cl.time;
620 // turn raindrop into simple spark and create delayedspawn splash effect
621 part->typeindex = pt_spark;
623 VectorMA(part->org, lifetime, part->vel, endvec);
624 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
625 part->die = cl.time + lifetime * trace.fraction;
626 part2 = CL_NewParticle(pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1);
629 part2->delayedspawn = part->die;
630 part2->die += part->die - cl.time;
631 for (i = rand() & 7;i < 10;i++)
633 part2 = CL_NewParticle(pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
636 part2->delayedspawn = part->die;
637 part2->die += part->die - cl.time;
642 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
644 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
647 VectorMA(part->org, lifetime, part->vel, endvec);
648 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
649 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
655 static void CL_ImmediateBloodStain(particle_t *part)
660 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
661 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
663 VectorCopy(part->vel, v);
665 staintex = part->staintexnum;
666 R_DecalSystem_SplatEntities(part->org, v, 1-((part->staincolor>>16)&255)*(1.0f/255.0f), 1-((part->staincolor>>8)&255)*(1.0f/255.0f), 1-((part->staincolor)&255)*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2);
669 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
670 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
672 VectorCopy(part->vel, v);
674 staintex = tex_blooddecal[rand()&7];
675 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);
679 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
683 entity_render_t *ent = &cl.entities[hitent].render;
684 unsigned char color[3];
685 if (!cl_decals.integer)
687 if (!ent->allowdecals)
690 l2 = (int)lhrandom(0.5, 256.5);
692 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
693 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
694 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
696 if (cl_decals_newsystem.integer)
698 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);
702 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
703 if (cl.free_decal >= cl.max_decals)
705 decal = &cl.decals[cl.free_decal++];
706 if (cl.num_decals < cl.free_decal)
707 cl.num_decals = cl.free_decal;
708 memset(decal, 0, sizeof(*decal));
709 decal->typeindex = pt_decal;
710 decal->texnum = texnum;
711 VectorMA(org, cl_decals_bias.value, normal, decal->org);
712 VectorCopy(normal, decal->normal);
714 decal->alpha = alpha;
715 decal->time2 = cl.time;
716 decal->color[0] = color[0];
717 decal->color[1] = color[1];
718 decal->color[2] = color[2];
719 decal->owner = hitent;
720 decal->clusterindex = -1000; // no vis culling unless we're sure
723 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
724 decal->ownermodel = cl.entities[decal->owner].render.model;
725 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
726 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
730 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
732 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
734 decal->clusterindex = leaf->clusterindex;
739 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
742 float bestfrac, bestorg[3], bestnormal[3];
744 int besthitent = 0, hitent;
747 for (i = 0;i < 32;i++)
750 VectorMA(org, maxdist, org2, org2);
751 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
752 // take the closest trace result that doesn't end up hitting a NOMARKS
753 // surface (sky for example)
754 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
756 bestfrac = trace.fraction;
758 VectorCopy(trace.endpos, bestorg);
759 VectorCopy(trace.plane.normal, bestnormal);
763 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
766 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
767 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
768 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)
771 matrix4x4_t tempmatrix;
773 VectorLerp(originmins, 0.5, originmaxs, center);
774 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
775 if (effectnameindex == EFFECT_SVC_PARTICLE)
777 if (cl_particles.integer)
779 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
781 CL_ParticleExplosion(center);
782 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
783 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
786 count *= cl_particles_quality.value;
787 for (;count > 0;count--)
789 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
790 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
795 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
796 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
797 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
798 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
799 else if (effectnameindex == EFFECT_TE_SPIKE)
801 if (cl_particles_bulletimpacts.integer)
803 if (cl_particles_quake.integer)
805 if (cl_particles_smoke.integer)
806 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
810 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
811 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
812 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
816 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
817 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
819 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
821 if (cl_particles_bulletimpacts.integer)
823 if (cl_particles_quake.integer)
825 if (cl_particles_smoke.integer)
826 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
830 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
831 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
832 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
836 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
837 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
838 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);
840 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
842 if (cl_particles_bulletimpacts.integer)
844 if (cl_particles_quake.integer)
846 if (cl_particles_smoke.integer)
847 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
851 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
852 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
853 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
857 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
858 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
860 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
862 if (cl_particles_bulletimpacts.integer)
864 if (cl_particles_quake.integer)
866 if (cl_particles_smoke.integer)
867 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
871 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
872 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
873 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
877 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
878 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
879 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);
881 else if (effectnameindex == EFFECT_TE_BLOOD)
883 if (!cl_particles_blood.integer)
885 if (cl_particles_quake.integer)
886 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
889 static double bloodaccumulator = 0;
890 qboolean immediatebloodstain = true;
891 //CL_NewParticle(pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD);
892 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
893 for (;bloodaccumulator > 0;bloodaccumulator--)
895 part = CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
896 if (immediatebloodstain && part)
898 immediatebloodstain = false;
899 CL_ImmediateBloodStain(part);
904 else if (effectnameindex == EFFECT_TE_SPARK)
905 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
906 else if (effectnameindex == EFFECT_TE_PLASMABURN)
908 // plasma scorch mark
909 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
910 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
911 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
913 else if (effectnameindex == EFFECT_TE_GUNSHOT)
915 if (cl_particles_bulletimpacts.integer)
917 if (cl_particles_quake.integer)
918 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
921 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
922 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
923 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
927 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
928 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
930 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
932 if (cl_particles_bulletimpacts.integer)
934 if (cl_particles_quake.integer)
935 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
938 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
939 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
940 CL_NewParticle(pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
944 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
945 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
946 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);
948 else if (effectnameindex == EFFECT_TE_EXPLOSION)
950 CL_ParticleExplosion(center);
951 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);
953 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
955 CL_ParticleExplosion(center);
956 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);
958 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
960 if (cl_particles_quake.integer)
963 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
966 CL_NewParticle(pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
968 CL_NewParticle(pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
972 CL_ParticleExplosion(center);
973 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);
975 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
976 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);
977 else if (effectnameindex == EFFECT_TE_FLAMEJET)
979 count *= cl_particles_quality.value;
981 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
983 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
985 float i, j, inc, vel;
988 inc = 8 / cl_particles_quality.value;
989 for (i = -128;i < 128;i += inc)
991 for (j = -128;j < 128;j += inc)
993 dir[0] = j + lhrandom(0, inc);
994 dir[1] = i + lhrandom(0, inc);
996 org[0] = center[0] + dir[0];
997 org[1] = center[1] + dir[1];
998 org[2] = center[2] + lhrandom(0, 64);
999 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
1000 CL_NewParticle(pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1004 else if (effectnameindex == EFFECT_TE_TELEPORT)
1006 float i, j, k, inc, vel;
1009 if (cl_particles_quake.integer)
1010 inc = 4 / cl_particles_quality.value;
1012 inc = 8 / cl_particles_quality.value;
1013 for (i = -16;i < 16;i += inc)
1015 for (j = -16;j < 16;j += inc)
1017 for (k = -24;k < 32;k += inc)
1019 VectorSet(dir, i*8, j*8, k*8);
1020 VectorNormalize(dir);
1021 vel = lhrandom(50, 113);
1022 if (cl_particles_quake.integer)
1023 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1025 CL_NewParticle(pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1029 if (!cl_particles_quake.integer)
1030 CL_NewParticle(pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1031 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);
1033 else if (effectnameindex == EFFECT_TE_TEI_G3)
1034 CL_NewParticle(pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1035 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1037 if (cl_particles_smoke.integer)
1039 count *= 0.25f * cl_particles_quality.value;
1041 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1044 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1046 CL_ParticleExplosion(center);
1047 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);
1049 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1052 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1053 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1054 if (cl_particles_smoke.integer)
1055 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1056 CL_NewParticle(pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1057 if (cl_particles_sparks.integer)
1058 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1059 CL_NewParticle(pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1060 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);
1062 else if (effectnameindex == EFFECT_EF_FLAME)
1064 count *= 300 * cl_particles_quality.value;
1066 CL_NewParticle(pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1067 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);
1069 else if (effectnameindex == EFFECT_EF_STARDUST)
1071 count *= 200 * cl_particles_quality.value;
1073 CL_NewParticle(pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1074 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);
1076 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1080 int smoke, blood, bubbles, r, color;
1082 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1085 Vector4Set(light, 0, 0, 0, 0);
1087 if (effectnameindex == EFFECT_TR_ROCKET)
1088 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1089 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1091 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1092 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1094 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1096 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1097 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1101 matrix4x4_t tempmatrix;
1102 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1103 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);
1104 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1108 if (!spawnparticles)
1111 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1114 VectorSubtract(originmaxs, originmins, dir);
1115 len = VectorNormalizeLength(dir);
1118 dec = -ent->persistent.trail_time;
1119 ent->persistent.trail_time += len;
1120 if (ent->persistent.trail_time < 0.01f)
1123 // if we skip out, leave it reset
1124 ent->persistent.trail_time = 0.0f;
1129 // advance into this frame to reach the first puff location
1130 VectorMA(originmins, dec, dir, pos);
1133 smoke = cl_particles.integer && cl_particles_smoke.integer;
1134 blood = cl_particles.integer && cl_particles_blood.integer;
1135 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1136 qd = 1.0f / cl_particles_quality.value;
1143 if (effectnameindex == EFFECT_TR_BLOOD)
1145 if (cl_particles_quake.integer)
1147 color = particlepalette[67 + (rand()&3)];
1148 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1153 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1156 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1158 if (cl_particles_quake.integer)
1161 color = particlepalette[67 + (rand()&3)];
1162 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1167 CL_NewParticle(pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1);
1173 if (effectnameindex == EFFECT_TR_ROCKET)
1175 if (cl_particles_quake.integer)
1178 color = particlepalette[ramp3[r]];
1179 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1183 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1184 CL_NewParticle(pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1187 else if (effectnameindex == EFFECT_TR_GRENADE)
1189 if (cl_particles_quake.integer)
1192 color = particlepalette[ramp3[r]];
1193 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1197 CL_NewParticle(pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1200 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1202 if (cl_particles_quake.integer)
1205 color = particlepalette[52 + (rand()&7)];
1206 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1207 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1209 else if (gamemode == GAME_GOODVSBAD2)
1212 CL_NewParticle(pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1216 color = particlepalette[20 + (rand()&7)];
1217 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1220 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1222 if (cl_particles_quake.integer)
1225 color = particlepalette[230 + (rand()&7)];
1226 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1227 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1231 color = particlepalette[226 + (rand()&7)];
1232 CL_NewParticle(pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1235 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1237 if (cl_particles_quake.integer)
1239 color = particlepalette[152 + (rand()&3)];
1240 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1242 else if (gamemode == GAME_GOODVSBAD2)
1245 CL_NewParticle(pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1247 else if (gamemode == GAME_PRYDON)
1250 CL_NewParticle(pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1253 CL_NewParticle(pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1255 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1258 CL_NewParticle(pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1260 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1263 CL_NewParticle(pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1265 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1266 CL_NewParticle(pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1270 if (effectnameindex == EFFECT_TR_ROCKET)
1271 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1272 else if (effectnameindex == EFFECT_TR_GRENADE)
1273 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1275 // advance to next time and position
1278 VectorMA (pos, dec, dir, pos);
1281 ent->persistent.trail_time = len;
1283 else if (developer.integer >= 1)
1284 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1287 // this is also called on point effects with spawndlight = true and
1288 // spawnparticles = true
1289 // it is called CL_ParticleTrail because most code does not want to supply
1290 // these parameters, only trail handling does
1291 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)
1294 qboolean found = false;
1295 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1297 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1298 return; // no such effect
1300 VectorLerp(originmins, 0.5, originmaxs, center);
1301 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1303 int effectinfoindex;
1306 particleeffectinfo_t *info;
1308 vec3_t centervelocity;
1314 qboolean underwater;
1315 qboolean immediatebloodstain;
1317 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1318 VectorLerp(originmins, 0.5, originmaxs, center);
1319 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1320 supercontents = CL_PointSuperContents(center);
1321 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1322 VectorSubtract(originmaxs, originmins, traildir);
1323 traillen = VectorLength(traildir);
1324 VectorNormalize(traildir);
1325 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1327 if (info->effectnameindex == effectnameindex)
1330 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1332 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1335 // spawn a dlight if requested
1336 if (info->lightradiusstart > 0 && spawndlight)
1338 matrix4x4_t tempmatrix;
1339 if (info->trailspacing > 0)
1340 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1342 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1343 if (info->lighttime > 0 && info->lightradiusfade > 0)
1345 // light flash (explosion, etc)
1346 // called when effect starts
1347 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);
1352 // called by CL_LinkNetworkEntity
1353 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1354 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);
1355 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1359 if (!spawnparticles)
1364 if (info->tex[1] > info->tex[0])
1366 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1367 tex = min(tex, info->tex[1] - 1);
1369 if(info->staintex[0] < 0)
1370 staintex = info->staintex[0];
1373 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1374 staintex = min(staintex, info->staintex[1] - 1);
1376 if (info->particletype == pt_decal)
1377 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]);
1378 else if (info->orientation == PARTICLE_HBEAM)
1379 CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1382 if (!cl_particles.integer)
1384 switch (info->particletype)
1386 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1387 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1388 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1389 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1390 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1391 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1394 VectorCopy(originmins, trailpos);
1395 if (info->trailspacing > 0)
1397 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1398 trailstep = info->trailspacing / cl_particles_quality.value;
1399 immediatebloodstain = false;
1403 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1405 immediatebloodstain = info->particletype == pt_blood || staintex;
1407 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1408 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1410 if (info->tex[1] > info->tex[0])
1412 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1413 tex = min(tex, info->tex[1] - 1);
1417 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1418 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1419 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1422 part = CL_NewParticle(info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex);
1423 if (immediatebloodstain && part)
1425 immediatebloodstain = false;
1426 CL_ImmediateBloodStain(part);
1429 VectorMA(trailpos, trailstep, traildir, trailpos);
1436 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1439 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)
1441 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1449 void CL_EntityParticles (const entity_t *ent)
1452 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1453 static vec3_t avelocities[NUMVERTEXNORMALS];
1454 if (!cl_particles.integer) return;
1455 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1457 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1459 if (!avelocities[0][0])
1460 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1461 avelocities[0][i] = lhrandom(0, 2.55);
1463 for (i = 0;i < NUMVERTEXNORMALS;i++)
1465 yaw = cl.time * avelocities[i][0];
1466 pitch = cl.time * avelocities[i][1];
1467 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1468 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1469 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1470 CL_NewParticle(pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1475 void CL_ReadPointFile_f (void)
1477 vec3_t org, leakorg;
1479 char *pointfile = NULL, *pointfilepos, *t, tchar;
1480 char name[MAX_OSPATH];
1485 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1486 strlcat (name, ".pts", sizeof (name));
1487 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1490 Con_Printf("Could not open %s\n", name);
1494 Con_Printf("Reading %s...\n", name);
1495 VectorClear(leakorg);
1498 pointfilepos = pointfile;
1499 while (*pointfilepos)
1501 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1506 while (*t && *t != '\n' && *t != '\r')
1510 #if _MSC_VER >= 1400
1511 #define sscanf sscanf_s
1513 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1519 VectorCopy(org, leakorg);
1522 if (cl.num_particles < cl.max_particles - 3)
1525 CL_NewParticle(pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1528 Mem_Free(pointfile);
1529 VectorCopy(leakorg, org);
1530 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1532 CL_NewParticle(pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1533 CL_NewParticle(pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1534 CL_NewParticle(pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1);
1539 CL_ParseParticleEffect
1541 Parse an effect out of the server message
1544 void CL_ParseParticleEffect (void)
1547 int i, count, msgcount, color;
1549 MSG_ReadVector(org, cls.protocol);
1550 for (i=0 ; i<3 ; i++)
1551 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1552 msgcount = MSG_ReadByte ();
1553 color = MSG_ReadByte ();
1555 if (msgcount == 255)
1560 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1565 CL_ParticleExplosion
1569 void CL_ParticleExplosion (const vec3_t org)
1575 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1576 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1578 if (cl_particles_quake.integer)
1580 for (i = 0;i < 1024;i++)
1586 color = particlepalette[ramp1[r]];
1587 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1591 color = particlepalette[ramp2[r]];
1592 CL_NewParticle(pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1598 i = CL_PointSuperContents(org);
1599 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1601 if (cl_particles.integer && cl_particles_bubbles.integer)
1602 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1603 CL_NewParticle(pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1607 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1609 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1613 for (k = 0;k < 16;k++)
1616 VectorMA(org, 128, v2, v);
1617 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1618 if (trace.fraction >= 0.1)
1621 VectorSubtract(trace.endpos, org, v2);
1622 VectorScale(v2, 2.0f, v2);
1623 CL_NewParticle(pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1629 if (cl_particles_explosions_shell.integer)
1630 R_NewExplosion(org);
1635 CL_ParticleExplosion2
1639 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1642 if (!cl_particles.integer) return;
1644 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1646 k = particlepalette[colorStart + (i % colorLength)];
1647 if (cl_particles_quake.integer)
1648 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1650 CL_NewParticle(pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1654 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1656 if (cl_particles_sparks.integer)
1658 sparkcount *= cl_particles_quality.value;
1659 while(sparkcount-- > 0)
1660 CL_NewParticle(pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1664 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1666 if (cl_particles_smoke.integer)
1668 smokecount *= cl_particles_quality.value;
1669 while(smokecount-- > 0)
1670 CL_NewParticle(pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1674 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)
1677 if (!cl_particles.integer) return;
1679 count = (int)(count * cl_particles_quality.value);
1682 k = particlepalette[colorbase + (rand()&3)];
1683 CL_NewParticle(pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1);
1687 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1690 float minz, maxz, lifetime = 30;
1691 if (!cl_particles.integer) return;
1692 if (dir[2] < 0) // falling
1694 minz = maxs[2] + dir[2] * 0.1;
1697 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1702 maxz = maxs[2] + dir[2] * 0.1;
1704 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1707 count = (int)(count * cl_particles_quality.value);
1712 if (!cl_particles_rain.integer) break;
1713 count *= 4; // ick, this should be in the mod or maps?
1717 k = particlepalette[colorbase + (rand()&3)];
1718 if (gamemode == GAME_GOODVSBAD2)
1719 CL_NewParticle(pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1721 CL_NewParticle(pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1);
1725 if (!cl_particles_snow.integer) break;
1728 k = particlepalette[colorbase + (rand()&3)];
1729 if (gamemode == GAME_GOODVSBAD2)
1730 CL_NewParticle(pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1732 CL_NewParticle(pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz), dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1);
1736 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1740 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1741 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1742 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1743 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1745 #define PARTICLETEXTURESIZE 64
1746 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1748 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1752 dz = 1 - (dx*dx+dy*dy);
1753 if (dz > 0) // it does hit the sphere
1757 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1758 VectorNormalize(normal);
1759 dot = DotProduct(normal, light);
1760 if (dot > 0.5) // interior reflection
1761 f += ((dot * 2) - 1);
1762 else if (dot < -0.5) // exterior reflection
1763 f += ((dot * -2) - 1);
1765 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1766 VectorNormalize(normal);
1767 dot = DotProduct(normal, light);
1768 if (dot > 0.5) // interior reflection
1769 f += ((dot * 2) - 1);
1770 else if (dot < -0.5) // exterior reflection
1771 f += ((dot * -2) - 1);
1773 f += 16; // just to give it a haze so you can see the outline
1774 f = bound(0, f, 255);
1775 return (unsigned char) f;
1781 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1782 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1784 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1785 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1786 *width = particlefontcellwidth;
1787 *height = particlefontcellheight;
1790 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1792 int basex, basey, w, h, y;
1793 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1794 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1795 Sys_Error("invalid particle texture size for autogenerating");
1796 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1797 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1800 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1803 float cx, cy, dx, dy, f, iradius;
1805 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1806 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1807 iradius = 1.0f / radius;
1808 alpha *= (1.0f / 255.0f);
1809 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1811 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1815 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1820 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1821 d[0] += (int)(f * (blue - d[0]));
1822 d[1] += (int)(f * (green - d[1]));
1823 d[2] += (int)(f * (red - d[2]));
1829 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1832 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1834 data[0] = bound(minb, data[0], maxb);
1835 data[1] = bound(ming, data[1], maxg);
1836 data[2] = bound(minr, data[2], maxr);
1840 void particletextureinvert(unsigned char *data)
1843 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1845 data[0] = 255 - data[0];
1846 data[1] = 255 - data[1];
1847 data[2] = 255 - data[2];
1851 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1852 static void R_InitBloodTextures (unsigned char *particletexturedata)
1855 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1858 for (i = 0;i < 8;i++)
1860 memset(&data[0][0][0], 255, sizeof(data));
1861 for (k = 0;k < 24;k++)
1862 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1863 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1864 particletextureinvert(&data[0][0][0]);
1865 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1869 for (i = 0;i < 8;i++)
1871 memset(&data[0][0][0], 255, sizeof(data));
1873 for (j = 1;j < 10;j++)
1874 for (k = min(j, m - 1);k < m;k++)
1875 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1876 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1877 particletextureinvert(&data[0][0][0]);
1878 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1883 //uncomment this to make engine save out particle font to a tga file when run
1884 //#define DUMPPARTICLEFONT
1886 static void R_InitParticleTexture (void)
1888 int x, y, d, i, k, m;
1889 int basex, basey, w, h;
1893 fs_offset_t filesize;
1895 // a note: decals need to modulate (multiply) the background color to
1896 // properly darken it (stain), and they need to be able to alpha fade,
1897 // this is a very difficult challenge because it means fading to white
1898 // (no change to background) rather than black (darkening everything
1899 // behind the whole decal polygon), and to accomplish this the texture is
1900 // inverted (dark red blood on white background becomes brilliant cyan
1901 // and white on black background) so we can alpha fade it to black, then
1902 // we invert it again during the blendfunc to make it work...
1904 #ifndef DUMPPARTICLEFONT
1905 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
1908 particlefonttexture = decalskinframe->base;
1909 // TODO maybe allow custom grid size?
1910 particlefontwidth = image_width;
1911 particlefontheight = image_height;
1912 particlefontcellwidth = image_width / 8;
1913 particlefontcellheight = image_height / 8;
1914 particlefontcols = 8;
1915 particlefontrows = 8;
1920 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1921 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1923 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1924 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1925 particlefontcols = 8;
1926 particlefontrows = 8;
1928 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1931 for (i = 0;i < 8;i++)
1933 memset(&data[0][0][0], 255, sizeof(data));
1936 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1938 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1939 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1941 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1943 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1944 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1946 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1947 d = (noise2[y][x] - 128) * 3 + 192;
1949 d = (int)(d * (1-(dx*dx+dy*dy)));
1950 d = (d * noise1[y][x]) >> 7;
1951 d = bound(0, d, 255);
1952 data[y][x][3] = (unsigned char) d;
1959 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1963 memset(&data[0][0][0], 255, sizeof(data));
1964 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1966 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1967 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1969 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1970 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1971 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1974 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1977 memset(&data[0][0][0], 255, sizeof(data));
1978 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1980 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1981 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1983 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1984 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1985 d = bound(0, d, 255);
1986 data[y][x][3] = (unsigned char) d;
1989 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1992 memset(&data[0][0][0], 255, sizeof(data));
1993 light[0] = 1;light[1] = 1;light[2] = 1;
1994 VectorNormalize(light);
1995 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1997 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1998 // stretch upper half of bubble by +50% and shrink lower half by -50%
1999 // (this gives an elongated teardrop shape)
2001 dy = (dy - 0.5f) * 2.0f;
2003 dy = (dy - 0.5f) / 1.5f;
2004 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2006 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2007 // shrink bubble width to half
2009 data[y][x][3] = shadebubble(dx, dy, light);
2012 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2015 memset(&data[0][0][0], 255, sizeof(data));
2016 light[0] = 1;light[1] = 1;light[2] = 1;
2017 VectorNormalize(light);
2018 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2020 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2021 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2023 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2024 data[y][x][3] = shadebubble(dx, dy, light);
2027 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2029 // Blood particles and blood decals
2030 R_InitBloodTextures (particletexturedata);
2033 for (i = 0;i < 8;i++)
2035 memset(&data[0][0][0], 255, sizeof(data));
2036 for (k = 0;k < 12;k++)
2037 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2038 for (k = 0;k < 3;k++)
2039 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2040 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2041 particletextureinvert(&data[0][0][0]);
2042 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2045 #ifdef DUMPPARTICLEFONT
2046 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2049 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2050 particlefonttexture = decalskinframe->base;
2052 Mem_Free(particletexturedata);
2054 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2056 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2057 particletexture[i].texture = particlefonttexture;
2058 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2059 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2060 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2061 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2064 #ifndef DUMPPARTICLEFONT
2065 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
2066 if (!particletexture[tex_beam].texture)
2069 unsigned char noise3[64][64], data2[64][16][4];
2071 fractalnoise(&noise3[0][0], 64, 4);
2073 for (y = 0;y < 64;y++)
2075 dy = (y - 0.5f*64) / (64*0.5f-1);
2076 for (x = 0;x < 16;x++)
2078 dx = (x - 0.5f*16) / (16*0.5f-2);
2079 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2080 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2081 data2[y][x][3] = 255;
2085 #ifdef DUMPPARTICLEFONT
2086 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2088 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2090 particletexture[tex_beam].s1 = 0;
2091 particletexture[tex_beam].t1 = 0;
2092 particletexture[tex_beam].s2 = 1;
2093 particletexture[tex_beam].t2 = 1;
2095 // now load an texcoord/texture override file
2096 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2103 if(!COM_ParseToken_Simple(&bufptr, true, false))
2105 if(!strcmp(com_token, "\n"))
2106 continue; // empty line
2107 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2108 particletexture[i].texture = particlefonttexture;
2110 if (!COM_ParseToken_Simple(&bufptr, true, false))
2112 if (!strcmp(com_token, "\n"))
2114 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2117 particletexture[i].s1 = atof(com_token);
2119 if (!COM_ParseToken_Simple(&bufptr, true, false))
2121 if (!strcmp(com_token, "\n"))
2123 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2126 particletexture[i].t1 = atof(com_token);
2128 if (!COM_ParseToken_Simple(&bufptr, true, false))
2130 if (!strcmp(com_token, "\n"))
2132 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2135 particletexture[i].s2 = atof(com_token);
2137 if (!COM_ParseToken_Simple(&bufptr, true, false))
2139 if (!strcmp(com_token, "\n"))
2141 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2144 particletexture[i].t2 = atof(com_token);
2150 static void r_part_start(void)
2153 // generate particlepalette for convenience from the main one
2154 for (i = 0;i < 256;i++)
2155 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2156 particletexturepool = R_AllocTexturePool();
2157 R_InitParticleTexture ();
2158 CL_Particles_LoadEffectInfo();
2161 static void r_part_shutdown(void)
2163 R_FreeTexturePool(&particletexturepool);
2166 static void r_part_newmap(void)
2169 R_SkinFrame_MarkUsed(decalskinframe);
2170 CL_Particles_LoadEffectInfo();
2173 #define BATCHSIZE 256
2174 unsigned short particle_elements[BATCHSIZE*6];
2176 void R_Particles_Init (void)
2179 for (i = 0;i < BATCHSIZE;i++)
2181 particle_elements[i*6+0] = i*4+0;
2182 particle_elements[i*6+1] = i*4+1;
2183 particle_elements[i*6+2] = i*4+2;
2184 particle_elements[i*6+3] = i*4+0;
2185 particle_elements[i*6+4] = i*4+2;
2186 particle_elements[i*6+5] = i*4+3;
2189 Cvar_RegisterVariable(&r_drawparticles);
2190 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2191 Cvar_RegisterVariable(&r_drawdecals);
2192 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2193 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2196 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2198 int surfacelistindex;
2200 float *v3f, *t2f, *c4f;
2201 particletexture_t *tex;
2202 float right[3], up[3], size, ca;
2203 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2204 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2206 RSurf_ActiveWorldEntity();
2208 r_refdef.stats.decals += numsurfaces;
2209 R_Mesh_ResetTextureState();
2210 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2211 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2212 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2213 R_SetupGenericShader(true);
2214 GL_DepthMask(false);
2215 GL_DepthRange(0, 1);
2216 GL_PolygonOffset(0, 0);
2218 GL_CullFace(GL_NONE);
2220 // generate all the vertices at once
2221 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2223 d = cl.decals + surfacelist[surfacelistindex];
2226 c4f = particle_color4f + 16*surfacelistindex;
2227 ca = d->alpha * alphascale;
2228 if (r_refdef.fogenabled)
2229 ca *= RSurf_FogVertex(d->org);
2230 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2231 Vector4Copy(c4f, c4f + 4);
2232 Vector4Copy(c4f, c4f + 8);
2233 Vector4Copy(c4f, c4f + 12);
2235 // calculate vertex positions
2236 size = d->size * cl_particles_size.value;
2237 VectorVectors(d->normal, right, up);
2238 VectorScale(right, size, right);
2239 VectorScale(up, size, up);
2240 v3f = particle_vertex3f + 12*surfacelistindex;
2241 v3f[ 0] = d->org[0] - right[0] - up[0];
2242 v3f[ 1] = d->org[1] - right[1] - up[1];
2243 v3f[ 2] = d->org[2] - right[2] - up[2];
2244 v3f[ 3] = d->org[0] - right[0] + up[0];
2245 v3f[ 4] = d->org[1] - right[1] + up[1];
2246 v3f[ 5] = d->org[2] - right[2] + up[2];
2247 v3f[ 6] = d->org[0] + right[0] + up[0];
2248 v3f[ 7] = d->org[1] + right[1] + up[1];
2249 v3f[ 8] = d->org[2] + right[2] + up[2];
2250 v3f[ 9] = d->org[0] + right[0] - up[0];
2251 v3f[10] = d->org[1] + right[1] - up[1];
2252 v3f[11] = d->org[2] + right[2] - up[2];
2254 // calculate texcoords
2255 tex = &particletexture[d->texnum];
2256 t2f = particle_texcoord2f + 8*surfacelistindex;
2257 t2f[0] = tex->s1;t2f[1] = tex->t2;
2258 t2f[2] = tex->s1;t2f[3] = tex->t1;
2259 t2f[4] = tex->s2;t2f[5] = tex->t1;
2260 t2f[6] = tex->s2;t2f[7] = tex->t2;
2263 // now render the decals all at once
2264 // (this assumes they all use one particle font texture!)
2265 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2266 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2267 GL_LockArrays(0, numsurfaces*4);
2268 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2269 GL_LockArrays(0, 0);
2272 void R_DrawDecals (void)
2275 int drawdecals = r_drawdecals.integer;
2281 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2282 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2284 // LordHavoc: early out conditions
2288 decalfade = frametime * 256 / cl_decals_fadetime.value;
2289 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2290 drawdist2 = drawdist2*drawdist2;
2292 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2294 if (!decal->typeindex)
2297 if (cl.time > decal->time2 + cl_decals_time.value)
2299 decal->alpha -= decalfade;
2300 if (decal->alpha <= 0)
2306 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2308 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2309 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2315 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2321 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))
2322 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2325 decal->typeindex = 0;
2326 if (cl.free_decal > i)
2330 // reduce cl.num_decals if possible
2331 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2334 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2336 decal_t *olddecals = cl.decals;
2337 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2338 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2339 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2340 Mem_Free(olddecals);
2344 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2346 int surfacelistindex;
2347 int batchstart, batchcount;
2348 const particle_t *p;
2350 rtexture_t *texture;
2351 float *v3f, *t2f, *c4f;
2352 particletexture_t *tex;
2353 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2354 float ambient[3], diffuse[3], diffusenormal[3];
2355 vec4_t colormultiplier;
2356 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2358 RSurf_ActiveWorldEntity();
2360 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));
2362 r_refdef.stats.particles += numsurfaces;
2363 R_Mesh_ResetTextureState();
2364 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2365 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2366 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2367 R_SetupGenericShader(true);
2368 GL_DepthMask(false);
2369 GL_DepthRange(0, 1);
2370 GL_PolygonOffset(0, 0);
2372 GL_CullFace(GL_NONE);
2374 // first generate all the vertices at once
2375 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2377 p = cl.particles + surfacelist[surfacelistindex];
2379 blendmode = p->blendmode;
2381 c4f[0] = p->color[0] * colormultiplier[0];
2382 c4f[1] = p->color[1] * colormultiplier[1];
2383 c4f[2] = p->color[2] * colormultiplier[2];
2384 c4f[3] = p->alpha * colormultiplier[3];
2387 case PBLEND_INVALID:
2390 // additive and modulate can just fade out in fog (this is correct)
2391 if (r_refdef.fogenabled)
2392 c4f[3] *= RSurf_FogVertex(p->org);
2393 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2400 // note: lighting is not cheap!
2401 if (particletype[p->typeindex].lighting)
2403 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2404 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2405 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2406 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2408 // mix in the fog color
2409 if (r_refdef.fogenabled)
2411 fog = RSurf_FogVertex(p->org);
2413 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2414 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2415 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2419 // copy the color into the other three vertices
2420 Vector4Copy(c4f, c4f + 4);
2421 Vector4Copy(c4f, c4f + 8);
2422 Vector4Copy(c4f, c4f + 12);
2424 size = p->size * cl_particles_size.value;
2425 tex = &particletexture[p->texnum];
2426 switch(p->orientation)
2428 case PARTICLE_INVALID:
2429 case PARTICLE_BILLBOARD:
2430 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2431 VectorScale(r_refdef.view.up, size, up);
2432 v3f[ 0] = p->org[0] - right[0] - up[0];
2433 v3f[ 1] = p->org[1] - right[1] - up[1];
2434 v3f[ 2] = p->org[2] - right[2] - up[2];
2435 v3f[ 3] = p->org[0] - right[0] + up[0];
2436 v3f[ 4] = p->org[1] - right[1] + up[1];
2437 v3f[ 5] = p->org[2] - right[2] + up[2];
2438 v3f[ 6] = p->org[0] + right[0] + up[0];
2439 v3f[ 7] = p->org[1] + right[1] + up[1];
2440 v3f[ 8] = p->org[2] + right[2] + up[2];
2441 v3f[ 9] = p->org[0] + right[0] - up[0];
2442 v3f[10] = p->org[1] + right[1] - up[1];
2443 v3f[11] = p->org[2] + right[2] - up[2];
2444 t2f[0] = tex->s1;t2f[1] = tex->t2;
2445 t2f[2] = tex->s1;t2f[3] = tex->t1;
2446 t2f[4] = tex->s2;t2f[5] = tex->t1;
2447 t2f[6] = tex->s2;t2f[7] = tex->t2;
2449 case PARTICLE_ORIENTED_DOUBLESIDED:
2450 VectorVectors(p->vel, right, up);
2451 VectorScale(right, size * p->stretch, right);
2452 VectorScale(up, size, up);
2453 v3f[ 0] = p->org[0] - right[0] - up[0];
2454 v3f[ 1] = p->org[1] - right[1] - up[1];
2455 v3f[ 2] = p->org[2] - right[2] - up[2];
2456 v3f[ 3] = p->org[0] - right[0] + up[0];
2457 v3f[ 4] = p->org[1] - right[1] + up[1];
2458 v3f[ 5] = p->org[2] - right[2] + up[2];
2459 v3f[ 6] = p->org[0] + right[0] + up[0];
2460 v3f[ 7] = p->org[1] + right[1] + up[1];
2461 v3f[ 8] = p->org[2] + right[2] + up[2];
2462 v3f[ 9] = p->org[0] + right[0] - up[0];
2463 v3f[10] = p->org[1] + right[1] - up[1];
2464 v3f[11] = p->org[2] + right[2] - up[2];
2465 t2f[0] = tex->s1;t2f[1] = tex->t2;
2466 t2f[2] = tex->s1;t2f[3] = tex->t1;
2467 t2f[4] = tex->s2;t2f[5] = tex->t1;
2468 t2f[6] = tex->s2;t2f[7] = tex->t2;
2470 case PARTICLE_SPARK:
2471 len = VectorLength(p->vel);
2472 VectorNormalize2(p->vel, up);
2473 lenfactor = p->stretch * 0.04 * len;
2474 if(lenfactor < size * 0.5)
2475 lenfactor = size * 0.5;
2476 VectorMA(p->org, -lenfactor, up, v);
2477 VectorMA(p->org, lenfactor, up, up2);
2478 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2479 t2f[0] = tex->s1;t2f[1] = tex->t2;
2480 t2f[2] = tex->s1;t2f[3] = tex->t1;
2481 t2f[4] = tex->s2;t2f[5] = tex->t1;
2482 t2f[6] = tex->s2;t2f[7] = tex->t2;
2484 case PARTICLE_VBEAM:
2485 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2486 VectorSubtract(p->vel, p->org, up);
2487 VectorNormalize(up);
2488 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2489 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2490 t2f[0] = tex->s2;t2f[1] = v[0];
2491 t2f[2] = tex->s1;t2f[3] = v[0];
2492 t2f[4] = tex->s1;t2f[5] = v[1];
2493 t2f[6] = tex->s2;t2f[7] = v[1];
2495 case PARTICLE_HBEAM:
2496 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2497 VectorSubtract(p->vel, p->org, up);
2498 VectorNormalize(up);
2499 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2500 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2501 t2f[0] = v[0];t2f[1] = tex->t1;
2502 t2f[2] = v[0];t2f[3] = tex->t2;
2503 t2f[4] = v[1];t2f[5] = tex->t2;
2504 t2f[6] = v[1];t2f[7] = tex->t1;
2509 // now render batches of particles based on blendmode and texture
2510 blendmode = PBLEND_INVALID;
2512 GL_LockArrays(0, numsurfaces*4);
2515 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2517 p = cl.particles + surfacelist[surfacelistindex];
2519 if (blendmode != p->blendmode)
2521 blendmode = p->blendmode;
2525 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2527 case PBLEND_INVALID:
2529 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2532 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2536 if (texture != particletexture[p->texnum].texture)
2538 texture = particletexture[p->texnum].texture;
2539 R_Mesh_TexBind(0, R_GetTexture(texture));
2542 // iterate until we find a change in settings
2543 batchstart = surfacelistindex++;
2544 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2546 p = cl.particles + surfacelist[surfacelistindex];
2547 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2551 batchcount = surfacelistindex - batchstart;
2552 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2554 GL_LockArrays(0, 0);
2557 void R_DrawParticles (void)
2560 int drawparticles = r_drawparticles.integer;
2561 float minparticledist;
2563 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2569 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2570 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2572 // LordHavoc: early out conditions
2573 if (!cl.num_particles)
2576 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2577 gravity = frametime * cl.movevars_gravity;
2578 dvel = 1+4*frametime;
2579 decalfade = frametime * 255 / cl_decals_fadetime.value;
2580 update = frametime > 0;
2581 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2582 drawdist2 = drawdist2*drawdist2;
2584 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2588 if (cl.free_particle > i)
2589 cl.free_particle = i;
2595 if (p->delayedspawn > cl.time)
2597 p->delayedspawn = 0;
2601 p->size += p->sizeincrease * frametime;
2602 p->alpha -= p->alphafade * frametime;
2604 if (p->alpha <= 0 || p->die <= cl.time)
2607 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2609 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2611 if (p->typeindex == pt_blood)
2612 p->size += frametime * 8;
2614 p->vel[2] -= p->gravity * gravity;
2615 f = 1.0f - min(p->liquidfriction * frametime, 1);
2616 VectorScale(p->vel, f, p->vel);
2620 p->vel[2] -= p->gravity * gravity;
2623 f = 1.0f - min(p->airfriction * frametime, 1);
2624 VectorScale(p->vel, f, p->vel);
2628 VectorCopy(p->org, oldorg);
2629 VectorMA(p->org, frametime, p->vel, p->org);
2630 if (p->bounce && cl.time >= p->delayedcollisions)
2632 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);
2633 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2634 // or if the trace hit something flagged as NOIMPACT
2635 // then remove the particle
2636 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2638 VectorCopy(trace.endpos, p->org);
2639 // react if the particle hit something
2640 if (trace.fraction < 1)
2642 VectorCopy(trace.endpos, p->org);
2644 if (p->staintexnum >= 0)
2646 // blood - splash on solid
2647 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2650 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2651 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2652 if (cl_decals.integer)
2654 // create a decal for the blood splat
2655 CL_SpawnDecalParticleForSurface(hitent, p->org, trace.plane.normal, 0xFFFFFF ^ p->staincolor, 0xFFFFFF ^ p->staincolor, p->staintexnum, p->size * 2, p->alpha); // staincolor needs to be inverted for decals!
2660 if (p->typeindex == pt_blood)
2662 // blood - splash on solid
2663 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2665 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2667 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)));
2668 if (cl_decals.integer)
2670 // create a decal for the blood splat
2671 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 * 2, p->alpha);
2676 else if (p->bounce < 0)
2678 // bounce -1 means remove on impact
2683 // anything else - bounce off solid
2684 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2685 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2686 if (DotProduct(p->vel, p->vel) < 0.03)
2687 VectorClear(p->vel);
2693 if (p->typeindex != pt_static)
2695 switch (p->typeindex)
2697 case pt_entityparticle:
2698 // particle that removes itself after one rendered frame
2705 a = CL_PointSuperContents(p->org);
2706 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2710 a = CL_PointSuperContents(p->org);
2711 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2715 a = CL_PointSuperContents(p->org);
2716 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2720 if (cl.time > p->time2)
2723 p->time2 = cl.time + (rand() & 3) * 0.1;
2724 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2725 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2727 a = CL_PointSuperContents(p->org);
2728 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2736 else if (p->delayedspawn)
2740 // don't render particles too close to the view (they chew fillrate)
2741 // also don't render particles behind the view (useless)
2742 // further checks to cull to the frustum would be too slow here
2743 switch(p->typeindex)
2746 // beams have no culling
2747 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2750 if(cl_particles_visculling.integer)
2751 if (!r_refdef.viewcache.world_novis)
2752 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2754 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2756 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2759 // anything else just has to be in front of the viewer and visible at this distance
2760 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2761 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2768 if (cl.free_particle > i)
2769 cl.free_particle = i;
2772 // reduce cl.num_particles if possible
2773 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2776 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2778 particle_t *oldparticles = cl.particles;
2779 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2780 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2781 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2782 Mem_Free(oldparticles);