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 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
128 static int particlepalette[256];
130 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
131 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
132 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
133 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
134 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
135 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
136 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
137 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
138 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
139 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
140 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
141 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
142 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
143 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
144 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
145 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
146 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
147 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
148 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
149 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
150 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
151 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
152 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
153 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
154 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
155 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
156 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
157 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
158 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
159 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
160 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
161 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
164 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
165 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
166 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
168 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
170 // particletexture_t is a rectangle in the particlefonttexture
171 typedef struct particletexture_s
174 float s1, t1, s2, t2;
178 static rtexturepool_t *particletexturepool;
179 static rtexture_t *particlefonttexture;
180 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
181 skinframe_t *decalskinframe;
183 // texture numbers in particle font
184 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
185 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
186 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
187 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
188 static const int tex_rainsplash = 32;
189 static const int tex_particle = 63;
190 static const int tex_bubble = 62;
191 static const int tex_raindrop = 61;
192 static const int tex_beam = 60;
194 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
195 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
196 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
197 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
198 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
199 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
200 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
201 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
202 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
203 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
204 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
205 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
206 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
207 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
208 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
209 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
210 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
211 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
212 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
213 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
214 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
215 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
216 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
217 cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "0", "enables new advanced decal system"};
218 cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"};
219 cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"};
222 void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend)
228 particleeffectinfo_t *info = NULL;
229 const char *text = textstart;
231 effectinfoindex = -1;
232 for (linenumber = 1;;linenumber++)
235 for (arrayindex = 0;arrayindex < 16;arrayindex++)
236 argv[arrayindex][0] = 0;
239 if (!COM_ParseToken_Simple(&text, true, false))
241 if (!strcmp(com_token, "\n"))
245 strlcpy(argv[argc], com_token, sizeof(argv[argc]));
251 #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;}
252 #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0)
253 #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex])
254 #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0)
255 #define readfloat(var) checkparms(2);var = atof(argv[1])
256 #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0
257 if (!strcmp(argv[0], "effect"))
262 if (effectinfoindex >= MAX_PARTICLEEFFECTINFO)
264 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
267 for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++)
269 if (particleeffectname[effectnameindex][0])
271 if (!strcmp(particleeffectname[effectnameindex], argv[1]))
276 strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex]));
280 // if we run out of names, abort
281 if (effectnameindex == MAX_PARTICLEEFFECTNAME)
283 Con_Printf("effectinfo.txt:%i: too many effects!\n", linenumber);
286 info = particleeffectinfo + effectinfoindex;
287 info->effectnameindex = effectnameindex;
288 info->particletype = pt_alphastatic;
289 info->blendmode = particletype[info->particletype].blendmode;
290 info->orientation = particletype[info->particletype].orientation;
291 info->tex[0] = tex_particle;
292 info->tex[1] = tex_particle;
293 info->color[0] = 0xFFFFFF;
294 info->color[1] = 0xFFFFFF;
298 info->alpha[1] = 256;
299 info->alpha[2] = 256;
300 info->time[0] = 9999;
301 info->time[1] = 9999;
302 VectorSet(info->lightcolor, 1, 1, 1);
303 info->lightshadow = true;
304 info->lighttime = 9999;
305 info->stretchfactor = 1;
306 info->staincolor[0] = (unsigned int)-1;
307 info->staincolor[1] = (unsigned int)-1;
308 info->staintex[0] = -1;
309 info->staintex[1] = -1;
311 else if (info == NULL)
313 Con_Printf("effectinfo.txt:%i: command %s encountered before effect\n", linenumber, argv[0]);
316 else if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);}
317 else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);}
318 else if (!strcmp(argv[0], "type"))
321 if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic;
322 else if (!strcmp(argv[1], "static")) info->particletype = pt_static;
323 else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark;
324 else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam;
325 else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain;
326 else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal;
327 else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow;
328 else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble;
329 else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;}
330 else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke;
331 else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal;
332 else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle;
333 else Con_Printf("effectinfo.txt:%i: unrecognized particle type %s\n", linenumber, argv[1]);
334 info->blendmode = particletype[info->particletype].blendmode;
335 info->orientation = particletype[info->particletype].orientation;
337 else if (!strcmp(argv[0], "blend"))
340 if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA;
341 else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD;
342 else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD;
343 else Con_Printf("effectinfo.txt:%i: unrecognized blendmode %s\n", linenumber, argv[1]);
345 else if (!strcmp(argv[0], "orientation"))
348 if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD;
349 else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK;
350 else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED;
351 else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM;
352 else Con_Printf("effectinfo.txt:%i: unrecognized orientation %s\n", linenumber, argv[1]);
354 else if (!strcmp(argv[0], "color")) {readints(info->color, 2);}
355 else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);}
356 else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);}
357 else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);}
358 else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);}
359 else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);}
360 else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);}
361 else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);}
362 else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);}
363 else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);}
364 else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);}
365 else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);}
366 else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);}
367 else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);}
368 else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);}
369 else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);}
370 else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);}
371 else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);}
372 else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);}
373 else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);}
374 else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);}
375 else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;}
376 else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;}
377 else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;}
378 else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);}
379 else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);}
380 else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);}
381 else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1;}
383 Con_Printf("effectinfo.txt:%i: skipping unknown command %s\n", linenumber, argv[0]);
392 int CL_ParticleEffectIndexForName(const char *name)
395 for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++)
396 if (!strcmp(particleeffectname[i], name))
401 const char *CL_ParticleEffectNameForIndex(int i)
403 if (i < 1 || i >= MAX_PARTICLEEFFECTNAME)
405 return particleeffectname[i];
408 // MUST match effectnameindex_t in client.h
409 static const char *standardeffectnames[EFFECT_TOTAL] =
433 "TE_TEI_BIGEXPLOSION",
449 void CL_Particles_LoadEffectInfo(void)
452 unsigned char *filedata;
453 fs_offset_t filesize;
454 memset(particleeffectinfo, 0, sizeof(particleeffectinfo));
455 memset(particleeffectname, 0, sizeof(particleeffectname));
456 for (i = 0;i < EFFECT_TOTAL;i++)
457 strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i]));
458 filedata = FS_LoadFile("effectinfo.txt", tempmempool, true, &filesize);
461 CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize);
471 void CL_ReadPointFile_f (void);
472 void CL_Particles_Init (void)
474 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)");
475 Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo, "reloads effectinfo.txt");
477 Cvar_RegisterVariable (&cl_particles);
478 Cvar_RegisterVariable (&cl_particles_quality);
479 Cvar_RegisterVariable (&cl_particles_alpha);
480 Cvar_RegisterVariable (&cl_particles_size);
481 Cvar_RegisterVariable (&cl_particles_quake);
482 Cvar_RegisterVariable (&cl_particles_blood);
483 Cvar_RegisterVariable (&cl_particles_blood_alpha);
484 Cvar_RegisterVariable (&cl_particles_blood_bloodhack);
485 Cvar_RegisterVariable (&cl_particles_explosions_sparks);
486 Cvar_RegisterVariable (&cl_particles_explosions_shell);
487 Cvar_RegisterVariable (&cl_particles_bulletimpacts);
488 Cvar_RegisterVariable (&cl_particles_rain);
489 Cvar_RegisterVariable (&cl_particles_snow);
490 Cvar_RegisterVariable (&cl_particles_smoke);
491 Cvar_RegisterVariable (&cl_particles_smoke_alpha);
492 Cvar_RegisterVariable (&cl_particles_smoke_alphafade);
493 Cvar_RegisterVariable (&cl_particles_sparks);
494 Cvar_RegisterVariable (&cl_particles_bubbles);
495 Cvar_RegisterVariable (&cl_particles_visculling);
496 Cvar_RegisterVariable (&cl_decals);
497 Cvar_RegisterVariable (&cl_decals_visculling);
498 Cvar_RegisterVariable (&cl_decals_time);
499 Cvar_RegisterVariable (&cl_decals_fadetime);
500 Cvar_RegisterVariable (&cl_decals_newsystem);
501 Cvar_RegisterVariable (&cl_decals_models);
502 Cvar_RegisterVariable (&cl_decals_bias);
505 void CL_Particles_Shutdown (void)
509 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha);
510 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2);
512 // list of all 26 parameters:
513 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
514 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
515 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
516 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
517 // palpha - opacity of particle as 0-255 (can be more than 255)
518 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
519 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
520 // pgravity - how much effect gravity has on the particle (0-1)
521 // 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
522 // px,py,pz - starting origin of particle
523 // pvx,pvy,pvz - starting velocity of particle
524 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
525 // blendmode - one of the PBLEND_ values
526 // orientation - one of the PARTICLE_ values
527 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
528 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
529 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)
534 if (!cl_particles.integer)
536 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
537 if (cl.free_particle >= cl.max_particles)
540 lifetime = palpha / min(1, palphafade);
541 part = &cl.particles[cl.free_particle++];
542 if (cl.num_particles < cl.free_particle)
543 cl.num_particles = cl.free_particle;
544 memset(part, 0, sizeof(*part));
545 part->typeindex = ptypeindex;
546 part->blendmode = blendmode;
547 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
549 particletexture_t *tex = &particletexture[ptex];
550 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
551 part->orientation = PARTICLE_VBEAM;
553 part->orientation = PARTICLE_HBEAM;
556 part->orientation = orientation;
557 l2 = (int)lhrandom(0.5, 256.5);
559 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
560 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
561 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
562 part->staintexnum = staintex;
563 if(staincolor1 >= 0 && staincolor2 >= 0)
565 l2 = (int)lhrandom(0.5, 256.5);
567 if(blendmode == PBLEND_INVMOD)
569 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
570 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
571 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
575 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
576 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
577 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
579 if(r > 0xFF) r = 0xFF;
580 if(g > 0xFF) g = 0xFF;
581 if(b > 0xFF) b = 0xFF;
585 r = part->color[0]; // -1 is shorthand for stain = particle color
589 part->staincolor = r * 65536 + g * 256 + b;
592 part->sizeincrease = psizeincrease;
593 part->alpha = palpha;
594 part->alphafade = palphafade;
595 part->gravity = pgravity;
596 part->bounce = pbounce;
597 part->stretch = stretch;
599 part->org[0] = px + originjitter * v[0];
600 part->org[1] = py + originjitter * v[1];
601 part->org[2] = pz + originjitter * v[2];
602 part->vel[0] = pvx + velocityjitter * v[0];
603 part->vel[1] = pvy + velocityjitter * v[1];
604 part->vel[2] = pvz + velocityjitter * v[2];
606 part->airfriction = pairfriction;
607 part->liquidfriction = pliquidfriction;
608 part->die = cl.time + lifetime;
609 part->delayedcollisions = 0;
610 part->qualityreduction = pqualityreduction;
611 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
612 if (part->typeindex == pt_rain)
616 float lifetime = part->die - cl.time;
619 // turn raindrop into simple spark and create delayedspawn splash effect
620 part->typeindex = pt_spark;
622 VectorMA(part->org, lifetime, part->vel, endvec);
623 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
624 part->die = cl.time + lifetime * trace.fraction;
625 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);
628 part2->delayedspawn = part->die;
629 part2->die += part->die - cl.time;
630 for (i = rand() & 7;i < 10;i++)
632 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);
635 part2->delayedspawn = part->die;
636 part2->die += part->die - cl.time;
641 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
643 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
646 VectorMA(part->org, lifetime, part->vel, endvec);
647 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
648 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
654 static void CL_ImmediateBloodStain(particle_t *part)
659 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
660 if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer)
662 VectorCopy(part->vel, v);
664 staintex = part->staintexnum;
665 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);
668 // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot
669 if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer)
671 VectorCopy(part->vel, v);
673 staintex = tex_blooddecal[rand()&7];
674 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);
678 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
682 entity_render_t *ent = &cl.entities[hitent].render;
683 unsigned char color[3];
684 if (!cl_decals.integer)
686 if (!ent->allowdecals)
689 l2 = (int)lhrandom(0.5, 256.5);
691 color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
692 color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
693 color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
695 if (cl_decals_newsystem.integer)
697 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);
701 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
702 if (cl.free_decal >= cl.max_decals)
704 decal = &cl.decals[cl.free_decal++];
705 if (cl.num_decals < cl.free_decal)
706 cl.num_decals = cl.free_decal;
707 memset(decal, 0, sizeof(*decal));
708 decal->typeindex = pt_decal;
709 decal->texnum = texnum;
710 VectorMA(org, cl_decals_bias.value, normal, decal->org);
711 VectorCopy(normal, decal->normal);
713 decal->alpha = alpha;
714 decal->time2 = cl.time;
715 decal->color[0] = color[0];
716 decal->color[1] = color[1];
717 decal->color[2] = color[2];
718 decal->owner = hitent;
719 decal->clusterindex = -1000; // no vis culling unless we're sure
722 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
723 decal->ownermodel = cl.entities[decal->owner].render.model;
724 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
725 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
729 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
731 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
733 decal->clusterindex = leaf->clusterindex;
738 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
741 float bestfrac, bestorg[3], bestnormal[3];
743 int besthitent = 0, hitent;
746 for (i = 0;i < 32;i++)
749 VectorMA(org, maxdist, org2, org2);
750 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
751 // take the closest trace result that doesn't end up hitting a NOMARKS
752 // surface (sky for example)
753 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
755 bestfrac = trace.fraction;
757 VectorCopy(trace.endpos, bestorg);
758 VectorCopy(trace.plane.normal, bestnormal);
762 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
765 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
766 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
767 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)
770 matrix4x4_t tempmatrix;
772 VectorLerp(originmins, 0.5, originmaxs, center);
773 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
774 if (effectnameindex == EFFECT_SVC_PARTICLE)
776 if (cl_particles.integer)
778 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
780 CL_ParticleExplosion(center);
781 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
782 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
785 count *= cl_particles_quality.value;
786 for (;count > 0;count--)
788 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
789 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);
794 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
795 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
796 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
797 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
798 else if (effectnameindex == EFFECT_TE_SPIKE)
800 if (cl_particles_bulletimpacts.integer)
802 if (cl_particles_quake.integer)
804 if (cl_particles_smoke.integer)
805 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
809 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
810 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
811 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);
815 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
816 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
818 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
820 if (cl_particles_bulletimpacts.integer)
822 if (cl_particles_quake.integer)
824 if (cl_particles_smoke.integer)
825 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
829 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
830 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
831 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);
835 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
836 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
837 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);
839 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
841 if (cl_particles_bulletimpacts.integer)
843 if (cl_particles_quake.integer)
845 if (cl_particles_smoke.integer)
846 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
850 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
851 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
852 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);
856 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
857 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
859 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
861 if (cl_particles_bulletimpacts.integer)
863 if (cl_particles_quake.integer)
865 if (cl_particles_smoke.integer)
866 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
870 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
871 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
872 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);
876 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
877 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
878 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);
880 else if (effectnameindex == EFFECT_TE_BLOOD)
882 if (!cl_particles_blood.integer)
884 if (cl_particles_quake.integer)
885 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
888 static double bloodaccumulator = 0;
889 qboolean immediatebloodstain = true;
890 //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);
891 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
892 for (;bloodaccumulator > 0;bloodaccumulator--)
894 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);
895 if (immediatebloodstain && part)
897 immediatebloodstain = false;
898 CL_ImmediateBloodStain(part);
903 else if (effectnameindex == EFFECT_TE_SPARK)
904 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
905 else if (effectnameindex == EFFECT_TE_PLASMABURN)
907 // plasma scorch mark
908 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
909 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
910 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
912 else if (effectnameindex == EFFECT_TE_GUNSHOT)
914 if (cl_particles_bulletimpacts.integer)
916 if (cl_particles_quake.integer)
917 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
920 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
921 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
922 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);
926 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
927 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
929 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
931 if (cl_particles_bulletimpacts.integer)
933 if (cl_particles_quake.integer)
934 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
937 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
938 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
939 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);
943 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
944 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
945 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);
947 else if (effectnameindex == EFFECT_TE_EXPLOSION)
949 CL_ParticleExplosion(center);
950 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);
952 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
954 CL_ParticleExplosion(center);
955 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);
957 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
959 if (cl_particles_quake.integer)
962 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
965 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);
967 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);
971 CL_ParticleExplosion(center);
972 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);
974 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
975 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);
976 else if (effectnameindex == EFFECT_TE_FLAMEJET)
978 count *= cl_particles_quality.value;
980 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);
982 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
984 float i, j, inc, vel;
987 inc = 8 / cl_particles_quality.value;
988 for (i = -128;i < 128;i += inc)
990 for (j = -128;j < 128;j += inc)
992 dir[0] = j + lhrandom(0, inc);
993 dir[1] = i + lhrandom(0, inc);
995 org[0] = center[0] + dir[0];
996 org[1] = center[1] + dir[1];
997 org[2] = center[2] + lhrandom(0, 64);
998 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
999 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);
1003 else if (effectnameindex == EFFECT_TE_TELEPORT)
1005 float i, j, k, inc, vel;
1008 if (cl_particles_quake.integer)
1009 inc = 4 / cl_particles_quality.value;
1011 inc = 8 / cl_particles_quality.value;
1012 for (i = -16;i < 16;i += inc)
1014 for (j = -16;j < 16;j += inc)
1016 for (k = -24;k < 32;k += inc)
1018 VectorSet(dir, i*8, j*8, k*8);
1019 VectorNormalize(dir);
1020 vel = lhrandom(50, 113);
1021 if (cl_particles_quake.integer)
1022 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);
1024 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);
1028 if (!cl_particles_quake.integer)
1029 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);
1030 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);
1032 else if (effectnameindex == EFFECT_TE_TEI_G3)
1033 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);
1034 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
1036 if (cl_particles_smoke.integer)
1038 count *= 0.25f * cl_particles_quality.value;
1040 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);
1043 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
1045 CL_ParticleExplosion(center);
1046 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);
1048 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
1051 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
1052 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1053 if (cl_particles_smoke.integer)
1054 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1055 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);
1056 if (cl_particles_sparks.integer)
1057 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1058 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);
1059 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);
1061 else if (effectnameindex == EFFECT_EF_FLAME)
1063 count *= 300 * cl_particles_quality.value;
1065 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);
1066 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);
1068 else if (effectnameindex == EFFECT_EF_STARDUST)
1070 count *= 200 * cl_particles_quality.value;
1072 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);
1073 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);
1075 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1079 int smoke, blood, bubbles, r, color;
1081 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1084 Vector4Set(light, 0, 0, 0, 0);
1086 if (effectnameindex == EFFECT_TR_ROCKET)
1087 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1088 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1090 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1091 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1093 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1095 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1096 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1100 matrix4x4_t tempmatrix;
1101 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1102 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);
1103 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1107 if (!spawnparticles)
1110 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1113 VectorSubtract(originmaxs, originmins, dir);
1114 len = VectorNormalizeLength(dir);
1117 dec = -ent->persistent.trail_time;
1118 ent->persistent.trail_time += len;
1119 if (ent->persistent.trail_time < 0.01f)
1122 // if we skip out, leave it reset
1123 ent->persistent.trail_time = 0.0f;
1128 // advance into this frame to reach the first puff location
1129 VectorMA(originmins, dec, dir, pos);
1132 smoke = cl_particles.integer && cl_particles_smoke.integer;
1133 blood = cl_particles.integer && cl_particles_blood.integer;
1134 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1135 qd = 1.0f / cl_particles_quality.value;
1142 if (effectnameindex == EFFECT_TR_BLOOD)
1144 if (cl_particles_quake.integer)
1146 color = particlepalette[67 + (rand()&3)];
1147 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);
1152 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);
1155 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1157 if (cl_particles_quake.integer)
1160 color = particlepalette[67 + (rand()&3)];
1161 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);
1166 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);
1172 if (effectnameindex == EFFECT_TR_ROCKET)
1174 if (cl_particles_quake.integer)
1177 color = particlepalette[ramp3[r]];
1178 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);
1182 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);
1183 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);
1186 else if (effectnameindex == EFFECT_TR_GRENADE)
1188 if (cl_particles_quake.integer)
1191 color = particlepalette[ramp3[r]];
1192 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);
1196 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);
1199 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1201 if (cl_particles_quake.integer)
1204 color = particlepalette[52 + (rand()&7)];
1205 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);
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);
1208 else if (gamemode == GAME_GOODVSBAD2)
1211 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);
1215 color = particlepalette[20 + (rand()&7)];
1216 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);
1219 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1221 if (cl_particles_quake.integer)
1224 color = particlepalette[230 + (rand()&7)];
1225 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);
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);
1230 color = particlepalette[226 + (rand()&7)];
1231 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);
1234 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1236 if (cl_particles_quake.integer)
1238 color = particlepalette[152 + (rand()&3)];
1239 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);
1241 else if (gamemode == GAME_GOODVSBAD2)
1244 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);
1246 else if (gamemode == GAME_PRYDON)
1249 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);
1252 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);
1254 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1257 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);
1259 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1262 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);
1264 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1265 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);
1269 if (effectnameindex == EFFECT_TR_ROCKET)
1270 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);
1271 else if (effectnameindex == EFFECT_TR_GRENADE)
1272 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);
1274 // advance to next time and position
1277 VectorMA (pos, dec, dir, pos);
1280 ent->persistent.trail_time = len;
1282 else if (developer.integer >= 1)
1283 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1286 // this is also called on point effects with spawndlight = true and
1287 // spawnparticles = true
1288 // it is called CL_ParticleTrail because most code does not want to supply
1289 // these parameters, only trail handling does
1290 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)
1293 qboolean found = false;
1294 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1296 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1297 return; // no such effect
1299 VectorLerp(originmins, 0.5, originmaxs, center);
1300 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1302 int effectinfoindex;
1305 particleeffectinfo_t *info;
1307 vec3_t centervelocity;
1313 qboolean underwater;
1314 qboolean immediatebloodstain;
1316 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1317 VectorLerp(originmins, 0.5, originmaxs, center);
1318 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1319 supercontents = CL_PointSuperContents(center);
1320 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1321 VectorSubtract(originmaxs, originmins, traildir);
1322 traillen = VectorLength(traildir);
1323 VectorNormalize(traildir);
1324 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1326 if (info->effectnameindex == effectnameindex)
1329 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1331 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1334 // spawn a dlight if requested
1335 if (info->lightradiusstart > 0 && spawndlight)
1337 matrix4x4_t tempmatrix;
1338 if (info->trailspacing > 0)
1339 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1341 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1342 if (info->lighttime > 0 && info->lightradiusfade > 0)
1344 // light flash (explosion, etc)
1345 // called when effect starts
1346 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);
1351 // called by CL_LinkNetworkEntity
1352 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1353 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);
1354 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1358 if (!spawnparticles)
1363 if (info->tex[1] > info->tex[0])
1365 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1366 tex = min(tex, info->tex[1] - 1);
1368 if(info->staintex[0] < 0)
1369 staintex = info->staintex[0];
1372 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1373 staintex = min(staintex, info->staintex[1] - 1);
1375 if (info->particletype == pt_decal)
1376 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]);
1377 else if (info->orientation == PARTICLE_HBEAM)
1378 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);
1381 if (!cl_particles.integer)
1383 switch (info->particletype)
1385 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1386 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1387 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1388 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1389 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1390 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1393 VectorCopy(originmins, trailpos);
1394 if (info->trailspacing > 0)
1396 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1397 trailstep = info->trailspacing / cl_particles_quality.value;
1398 immediatebloodstain = false;
1402 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1404 immediatebloodstain = info->particletype == pt_blood || staintex;
1406 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1407 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1409 if (info->tex[1] > info->tex[0])
1411 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1412 tex = min(tex, info->tex[1] - 1);
1416 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1417 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1418 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1421 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);
1422 if (immediatebloodstain && part)
1424 immediatebloodstain = false;
1425 CL_ImmediateBloodStain(part);
1428 VectorMA(trailpos, trailstep, traildir, trailpos);
1435 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1438 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)
1440 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1448 void CL_EntityParticles (const entity_t *ent)
1451 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1452 static vec3_t avelocities[NUMVERTEXNORMALS];
1453 if (!cl_particles.integer) return;
1454 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1456 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1458 if (!avelocities[0][0])
1459 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1460 avelocities[0][i] = lhrandom(0, 2.55);
1462 for (i = 0;i < NUMVERTEXNORMALS;i++)
1464 yaw = cl.time * avelocities[i][0];
1465 pitch = cl.time * avelocities[i][1];
1466 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1467 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1468 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1469 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);
1474 void CL_ReadPointFile_f (void)
1476 vec3_t org, leakorg;
1478 char *pointfile = NULL, *pointfilepos, *t, tchar;
1479 char name[MAX_OSPATH];
1484 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1485 strlcat (name, ".pts", sizeof (name));
1486 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1489 Con_Printf("Could not open %s\n", name);
1493 Con_Printf("Reading %s...\n", name);
1494 VectorClear(leakorg);
1497 pointfilepos = pointfile;
1498 while (*pointfilepos)
1500 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1505 while (*t && *t != '\n' && *t != '\r')
1509 #if _MSC_VER >= 1400
1510 #define sscanf sscanf_s
1512 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1518 VectorCopy(org, leakorg);
1521 if (cl.num_particles < cl.max_particles - 3)
1524 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);
1527 Mem_Free(pointfile);
1528 VectorCopy(leakorg, org);
1529 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1531 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);
1532 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);
1533 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);
1538 CL_ParseParticleEffect
1540 Parse an effect out of the server message
1543 void CL_ParseParticleEffect (void)
1546 int i, count, msgcount, color;
1548 MSG_ReadVector(org, cls.protocol);
1549 for (i=0 ; i<3 ; i++)
1550 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1551 msgcount = MSG_ReadByte ();
1552 color = MSG_ReadByte ();
1554 if (msgcount == 255)
1559 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1564 CL_ParticleExplosion
1568 void CL_ParticleExplosion (const vec3_t org)
1574 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1575 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1577 if (cl_particles_quake.integer)
1579 for (i = 0;i < 1024;i++)
1585 color = particlepalette[ramp1[r]];
1586 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);
1590 color = particlepalette[ramp2[r]];
1591 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);
1597 i = CL_PointSuperContents(org);
1598 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1600 if (cl_particles.integer && cl_particles_bubbles.integer)
1601 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1602 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);
1606 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1608 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1612 for (k = 0;k < 16;k++)
1615 VectorMA(org, 128, v2, v);
1616 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1617 if (trace.fraction >= 0.1)
1620 VectorSubtract(trace.endpos, org, v2);
1621 VectorScale(v2, 2.0f, v2);
1622 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);
1628 if (cl_particles_explosions_shell.integer)
1629 R_NewExplosion(org);
1634 CL_ParticleExplosion2
1638 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1641 if (!cl_particles.integer) return;
1643 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1645 k = particlepalette[colorStart + (i % colorLength)];
1646 if (cl_particles_quake.integer)
1647 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);
1649 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);
1653 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1655 if (cl_particles_sparks.integer)
1657 sparkcount *= cl_particles_quality.value;
1658 while(sparkcount-- > 0)
1659 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);
1663 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1665 if (cl_particles_smoke.integer)
1667 smokecount *= cl_particles_quality.value;
1668 while(smokecount-- > 0)
1669 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);
1673 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)
1676 if (!cl_particles.integer) return;
1678 count = (int)(count * cl_particles_quality.value);
1681 k = particlepalette[colorbase + (rand()&3)];
1682 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);
1686 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1689 float minz, maxz, lifetime = 30;
1690 if (!cl_particles.integer) return;
1691 if (dir[2] < 0) // falling
1693 minz = maxs[2] + dir[2] * 0.1;
1696 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1701 maxz = maxs[2] + dir[2] * 0.1;
1703 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1706 count = (int)(count * cl_particles_quality.value);
1711 if (!cl_particles_rain.integer) break;
1712 count *= 4; // ick, this should be in the mod or maps?
1716 k = particlepalette[colorbase + (rand()&3)];
1717 if (gamemode == GAME_GOODVSBAD2)
1718 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);
1720 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);
1724 if (!cl_particles_snow.integer) break;
1727 k = particlepalette[colorbase + (rand()&3)];
1728 if (gamemode == GAME_GOODVSBAD2)
1729 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);
1731 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);
1735 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1739 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1740 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1741 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1742 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1744 #define PARTICLETEXTURESIZE 64
1745 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1747 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1751 dz = 1 - (dx*dx+dy*dy);
1752 if (dz > 0) // it does hit the sphere
1756 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1757 VectorNormalize(normal);
1758 dot = DotProduct(normal, light);
1759 if (dot > 0.5) // interior reflection
1760 f += ((dot * 2) - 1);
1761 else if (dot < -0.5) // exterior reflection
1762 f += ((dot * -2) - 1);
1764 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1765 VectorNormalize(normal);
1766 dot = DotProduct(normal, light);
1767 if (dot > 0.5) // interior reflection
1768 f += ((dot * 2) - 1);
1769 else if (dot < -0.5) // exterior reflection
1770 f += ((dot * -2) - 1);
1772 f += 16; // just to give it a haze so you can see the outline
1773 f = bound(0, f, 255);
1774 return (unsigned char) f;
1780 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1781 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1783 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1784 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1785 *width = particlefontcellwidth;
1786 *height = particlefontcellheight;
1789 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1791 int basex, basey, w, h, y;
1792 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1793 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1794 Sys_Error("invalid particle texture size for autogenerating");
1795 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1796 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1799 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1802 float cx, cy, dx, dy, f, iradius;
1804 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1805 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1806 iradius = 1.0f / radius;
1807 alpha *= (1.0f / 255.0f);
1808 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1810 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1814 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1819 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1820 d[0] += (int)(f * (blue - d[0]));
1821 d[1] += (int)(f * (green - d[1]));
1822 d[2] += (int)(f * (red - d[2]));
1828 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1831 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1833 data[0] = bound(minb, data[0], maxb);
1834 data[1] = bound(ming, data[1], maxg);
1835 data[2] = bound(minr, data[2], maxr);
1839 void particletextureinvert(unsigned char *data)
1842 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1844 data[0] = 255 - data[0];
1845 data[1] = 255 - data[1];
1846 data[2] = 255 - data[2];
1850 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1851 static void R_InitBloodTextures (unsigned char *particletexturedata)
1854 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1857 for (i = 0;i < 8;i++)
1859 memset(&data[0][0][0], 255, sizeof(data));
1860 for (k = 0;k < 24;k++)
1861 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1862 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1863 particletextureinvert(&data[0][0][0]);
1864 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1868 for (i = 0;i < 8;i++)
1870 memset(&data[0][0][0], 255, sizeof(data));
1872 for (j = 1;j < 10;j++)
1873 for (k = min(j, m - 1);k < m;k++)
1874 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1875 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1876 particletextureinvert(&data[0][0][0]);
1877 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1882 //uncomment this to make engine save out particle font to a tga file when run
1883 //#define DUMPPARTICLEFONT
1885 static void R_InitParticleTexture (void)
1887 int x, y, d, i, k, m;
1888 int basex, basey, w, h;
1892 fs_offset_t filesize;
1894 // a note: decals need to modulate (multiply) the background color to
1895 // properly darken it (stain), and they need to be able to alpha fade,
1896 // this is a very difficult challenge because it means fading to white
1897 // (no change to background) rather than black (darkening everything
1898 // behind the whole decal polygon), and to accomplish this the texture is
1899 // inverted (dark red blood on white background becomes brilliant cyan
1900 // and white on black background) so we can alpha fade it to black, then
1901 // we invert it again during the blendfunc to make it work...
1903 #ifndef DUMPPARTICLEFONT
1904 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
1907 particlefonttexture = decalskinframe->base;
1908 // TODO maybe allow custom grid size?
1909 particlefontwidth = image_width;
1910 particlefontheight = image_height;
1911 particlefontcellwidth = image_width / 8;
1912 particlefontcellheight = image_height / 8;
1913 particlefontcols = 8;
1914 particlefontrows = 8;
1919 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1920 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1922 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1923 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1924 particlefontcols = 8;
1925 particlefontrows = 8;
1927 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1930 for (i = 0;i < 8;i++)
1932 memset(&data[0][0][0], 255, sizeof(data));
1935 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1937 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1938 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1940 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1942 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1943 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1945 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1946 d = (noise2[y][x] - 128) * 3 + 192;
1948 d = (int)(d * (1-(dx*dx+dy*dy)));
1949 d = (d * noise1[y][x]) >> 7;
1950 d = bound(0, d, 255);
1951 data[y][x][3] = (unsigned char) d;
1958 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1962 memset(&data[0][0][0], 255, sizeof(data));
1963 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1965 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1966 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1968 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1969 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1970 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1973 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1976 memset(&data[0][0][0], 255, sizeof(data));
1977 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1979 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1980 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1982 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1983 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1984 d = bound(0, d, 255);
1985 data[y][x][3] = (unsigned char) d;
1988 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1991 memset(&data[0][0][0], 255, sizeof(data));
1992 light[0] = 1;light[1] = 1;light[2] = 1;
1993 VectorNormalize(light);
1994 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1996 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1997 // stretch upper half of bubble by +50% and shrink lower half by -50%
1998 // (this gives an elongated teardrop shape)
2000 dy = (dy - 0.5f) * 2.0f;
2002 dy = (dy - 0.5f) / 1.5f;
2003 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2005 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2006 // shrink bubble width to half
2008 data[y][x][3] = shadebubble(dx, dy, light);
2011 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
2014 memset(&data[0][0][0], 255, sizeof(data));
2015 light[0] = 1;light[1] = 1;light[2] = 1;
2016 VectorNormalize(light);
2017 for (y = 0;y < PARTICLETEXTURESIZE;y++)
2019 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2020 for (x = 0;x < PARTICLETEXTURESIZE;x++)
2022 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
2023 data[y][x][3] = shadebubble(dx, dy, light);
2026 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
2028 // Blood particles and blood decals
2029 R_InitBloodTextures (particletexturedata);
2032 for (i = 0;i < 8;i++)
2034 memset(&data[0][0][0], 255, sizeof(data));
2035 for (k = 0;k < 12;k++)
2036 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
2037 for (k = 0;k < 3;k++)
2038 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
2039 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
2040 particletextureinvert(&data[0][0][0]);
2041 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
2044 #ifdef DUMPPARTICLEFONT
2045 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
2048 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
2049 particlefonttexture = decalskinframe->base;
2051 Mem_Free(particletexturedata);
2053 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
2055 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
2056 particletexture[i].texture = particlefonttexture;
2057 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
2058 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
2059 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
2060 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
2063 #ifndef DUMPPARTICLEFONT
2064 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
2065 if (!particletexture[tex_beam].texture)
2068 unsigned char noise3[64][64], data2[64][16][4];
2070 fractalnoise(&noise3[0][0], 64, 4);
2072 for (y = 0;y < 64;y++)
2074 dy = (y - 0.5f*64) / (64*0.5f-1);
2075 for (x = 0;x < 16;x++)
2077 dx = (x - 0.5f*16) / (16*0.5f-2);
2078 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2079 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2080 data2[y][x][3] = 255;
2084 #ifdef DUMPPARTICLEFONT
2085 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2087 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2089 particletexture[tex_beam].s1 = 0;
2090 particletexture[tex_beam].t1 = 0;
2091 particletexture[tex_beam].s2 = 1;
2092 particletexture[tex_beam].t2 = 1;
2094 // now load an texcoord/texture override file
2095 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2102 if(!COM_ParseToken_Simple(&bufptr, true, false))
2104 if(!strcmp(com_token, "\n"))
2105 continue; // empty line
2106 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2107 particletexture[i].texture = particlefonttexture;
2109 if (!COM_ParseToken_Simple(&bufptr, true, false))
2111 if (!strcmp(com_token, "\n"))
2113 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2116 particletexture[i].s1 = atof(com_token);
2118 if (!COM_ParseToken_Simple(&bufptr, true, false))
2120 if (!strcmp(com_token, "\n"))
2122 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2125 particletexture[i].t1 = atof(com_token);
2127 if (!COM_ParseToken_Simple(&bufptr, true, false))
2129 if (!strcmp(com_token, "\n"))
2131 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2134 particletexture[i].s2 = atof(com_token);
2136 if (!COM_ParseToken_Simple(&bufptr, true, false))
2138 if (!strcmp(com_token, "\n"))
2140 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2143 particletexture[i].t2 = atof(com_token);
2149 static void r_part_start(void)
2152 // generate particlepalette for convenience from the main one
2153 for (i = 0;i < 256;i++)
2154 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2155 particletexturepool = R_AllocTexturePool();
2156 R_InitParticleTexture ();
2157 CL_Particles_LoadEffectInfo();
2160 static void r_part_shutdown(void)
2162 R_FreeTexturePool(&particletexturepool);
2165 static void r_part_newmap(void)
2168 R_SkinFrame_MarkUsed(decalskinframe);
2169 CL_Particles_LoadEffectInfo();
2172 #define BATCHSIZE 256
2173 unsigned short particle_elements[BATCHSIZE*6];
2175 void R_Particles_Init (void)
2178 for (i = 0;i < BATCHSIZE;i++)
2180 particle_elements[i*6+0] = i*4+0;
2181 particle_elements[i*6+1] = i*4+1;
2182 particle_elements[i*6+2] = i*4+2;
2183 particle_elements[i*6+3] = i*4+0;
2184 particle_elements[i*6+4] = i*4+2;
2185 particle_elements[i*6+5] = i*4+3;
2188 Cvar_RegisterVariable(&r_drawparticles);
2189 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2190 Cvar_RegisterVariable(&r_drawdecals);
2191 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2192 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2195 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2197 int surfacelistindex;
2199 float *v3f, *t2f, *c4f;
2200 particletexture_t *tex;
2201 float right[3], up[3], size, ca;
2202 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2203 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2205 RSurf_ActiveWorldEntity();
2207 r_refdef.stats.decals += numsurfaces;
2208 R_Mesh_ResetTextureState();
2209 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2210 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2211 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2212 R_SetupGenericShader(true);
2213 GL_DepthMask(false);
2214 GL_DepthRange(0, 1);
2215 GL_PolygonOffset(0, 0);
2217 GL_CullFace(GL_NONE);
2219 // generate all the vertices at once
2220 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2222 d = cl.decals + surfacelist[surfacelistindex];
2225 c4f = particle_color4f + 16*surfacelistindex;
2226 ca = d->alpha * alphascale;
2227 if (r_refdef.fogenabled)
2228 ca *= RSurf_FogVertex(d->org);
2229 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2230 Vector4Copy(c4f, c4f + 4);
2231 Vector4Copy(c4f, c4f + 8);
2232 Vector4Copy(c4f, c4f + 12);
2234 // calculate vertex positions
2235 size = d->size * cl_particles_size.value;
2236 VectorVectors(d->normal, right, up);
2237 VectorScale(right, size, right);
2238 VectorScale(up, size, up);
2239 v3f = particle_vertex3f + 12*surfacelistindex;
2240 v3f[ 0] = d->org[0] - right[0] - up[0];
2241 v3f[ 1] = d->org[1] - right[1] - up[1];
2242 v3f[ 2] = d->org[2] - right[2] - up[2];
2243 v3f[ 3] = d->org[0] - right[0] + up[0];
2244 v3f[ 4] = d->org[1] - right[1] + up[1];
2245 v3f[ 5] = d->org[2] - right[2] + up[2];
2246 v3f[ 6] = d->org[0] + right[0] + up[0];
2247 v3f[ 7] = d->org[1] + right[1] + up[1];
2248 v3f[ 8] = d->org[2] + right[2] + up[2];
2249 v3f[ 9] = d->org[0] + right[0] - up[0];
2250 v3f[10] = d->org[1] + right[1] - up[1];
2251 v3f[11] = d->org[2] + right[2] - up[2];
2253 // calculate texcoords
2254 tex = &particletexture[d->texnum];
2255 t2f = particle_texcoord2f + 8*surfacelistindex;
2256 t2f[0] = tex->s1;t2f[1] = tex->t2;
2257 t2f[2] = tex->s1;t2f[3] = tex->t1;
2258 t2f[4] = tex->s2;t2f[5] = tex->t1;
2259 t2f[6] = tex->s2;t2f[7] = tex->t2;
2262 // now render the decals all at once
2263 // (this assumes they all use one particle font texture!)
2264 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2265 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2266 GL_LockArrays(0, numsurfaces*4);
2267 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2268 GL_LockArrays(0, 0);
2271 void R_DrawDecals (void)
2274 int drawdecals = r_drawdecals.integer;
2280 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2281 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2283 // LordHavoc: early out conditions
2287 decalfade = frametime * 256 / cl_decals_fadetime.value;
2288 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2289 drawdist2 = drawdist2*drawdist2;
2291 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2293 if (!decal->typeindex)
2296 if (cl.time > decal->time2 + cl_decals_time.value)
2298 decal->alpha -= decalfade;
2299 if (decal->alpha <= 0)
2305 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2307 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2308 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2314 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2320 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))
2321 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2324 decal->typeindex = 0;
2325 if (cl.free_decal > i)
2329 // reduce cl.num_decals if possible
2330 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2333 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2335 decal_t *olddecals = cl.decals;
2336 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2337 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2338 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2339 Mem_Free(olddecals);
2343 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2345 int surfacelistindex;
2346 int batchstart, batchcount;
2347 const particle_t *p;
2349 rtexture_t *texture;
2350 float *v3f, *t2f, *c4f;
2351 particletexture_t *tex;
2352 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2353 float ambient[3], diffuse[3], diffusenormal[3];
2354 vec4_t colormultiplier;
2355 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2357 RSurf_ActiveWorldEntity();
2359 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));
2361 r_refdef.stats.particles += numsurfaces;
2362 R_Mesh_ResetTextureState();
2363 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2364 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2365 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2366 R_SetupGenericShader(true);
2367 GL_DepthMask(false);
2368 GL_DepthRange(0, 1);
2369 GL_PolygonOffset(0, 0);
2371 GL_CullFace(GL_NONE);
2373 // first generate all the vertices at once
2374 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2376 p = cl.particles + surfacelist[surfacelistindex];
2378 blendmode = p->blendmode;
2380 c4f[0] = p->color[0] * colormultiplier[0];
2381 c4f[1] = p->color[1] * colormultiplier[1];
2382 c4f[2] = p->color[2] * colormultiplier[2];
2383 c4f[3] = p->alpha * colormultiplier[3];
2386 case PBLEND_INVALID:
2389 // additive and modulate can just fade out in fog (this is correct)
2390 if (r_refdef.fogenabled)
2391 c4f[3] *= RSurf_FogVertex(p->org);
2392 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2399 // note: lighting is not cheap!
2400 if (particletype[p->typeindex].lighting)
2402 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2403 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2404 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2405 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2407 // mix in the fog color
2408 if (r_refdef.fogenabled)
2410 fog = RSurf_FogVertex(p->org);
2412 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2413 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2414 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2418 // copy the color into the other three vertices
2419 Vector4Copy(c4f, c4f + 4);
2420 Vector4Copy(c4f, c4f + 8);
2421 Vector4Copy(c4f, c4f + 12);
2423 size = p->size * cl_particles_size.value;
2424 tex = &particletexture[p->texnum];
2425 switch(p->orientation)
2427 case PARTICLE_INVALID:
2428 case PARTICLE_BILLBOARD:
2429 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2430 VectorScale(r_refdef.view.up, size, up);
2431 v3f[ 0] = p->org[0] - right[0] - up[0];
2432 v3f[ 1] = p->org[1] - right[1] - up[1];
2433 v3f[ 2] = p->org[2] - right[2] - up[2];
2434 v3f[ 3] = p->org[0] - right[0] + up[0];
2435 v3f[ 4] = p->org[1] - right[1] + up[1];
2436 v3f[ 5] = p->org[2] - right[2] + up[2];
2437 v3f[ 6] = p->org[0] + right[0] + up[0];
2438 v3f[ 7] = p->org[1] + right[1] + up[1];
2439 v3f[ 8] = p->org[2] + right[2] + up[2];
2440 v3f[ 9] = p->org[0] + right[0] - up[0];
2441 v3f[10] = p->org[1] + right[1] - up[1];
2442 v3f[11] = p->org[2] + right[2] - up[2];
2443 t2f[0] = tex->s1;t2f[1] = tex->t2;
2444 t2f[2] = tex->s1;t2f[3] = tex->t1;
2445 t2f[4] = tex->s2;t2f[5] = tex->t1;
2446 t2f[6] = tex->s2;t2f[7] = tex->t2;
2448 case PARTICLE_ORIENTED_DOUBLESIDED:
2449 VectorVectors(p->vel, right, up);
2450 VectorScale(right, size * p->stretch, right);
2451 VectorScale(up, size, up);
2452 v3f[ 0] = p->org[0] - right[0] - up[0];
2453 v3f[ 1] = p->org[1] - right[1] - up[1];
2454 v3f[ 2] = p->org[2] - right[2] - up[2];
2455 v3f[ 3] = p->org[0] - right[0] + up[0];
2456 v3f[ 4] = p->org[1] - right[1] + up[1];
2457 v3f[ 5] = p->org[2] - right[2] + up[2];
2458 v3f[ 6] = p->org[0] + right[0] + up[0];
2459 v3f[ 7] = p->org[1] + right[1] + up[1];
2460 v3f[ 8] = p->org[2] + right[2] + up[2];
2461 v3f[ 9] = p->org[0] + right[0] - up[0];
2462 v3f[10] = p->org[1] + right[1] - up[1];
2463 v3f[11] = p->org[2] + right[2] - up[2];
2464 t2f[0] = tex->s1;t2f[1] = tex->t2;
2465 t2f[2] = tex->s1;t2f[3] = tex->t1;
2466 t2f[4] = tex->s2;t2f[5] = tex->t1;
2467 t2f[6] = tex->s2;t2f[7] = tex->t2;
2469 case PARTICLE_SPARK:
2470 len = VectorLength(p->vel);
2471 VectorNormalize2(p->vel, up);
2472 lenfactor = p->stretch * 0.04 * len;
2473 if(lenfactor < size * 0.5)
2474 lenfactor = size * 0.5;
2475 VectorMA(p->org, -lenfactor, up, v);
2476 VectorMA(p->org, lenfactor, up, up2);
2477 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2478 t2f[0] = tex->s1;t2f[1] = tex->t2;
2479 t2f[2] = tex->s1;t2f[3] = tex->t1;
2480 t2f[4] = tex->s2;t2f[5] = tex->t1;
2481 t2f[6] = tex->s2;t2f[7] = tex->t2;
2483 case PARTICLE_VBEAM:
2484 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2485 VectorSubtract(p->vel, p->org, up);
2486 VectorNormalize(up);
2487 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2488 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2489 t2f[0] = tex->s2;t2f[1] = v[0];
2490 t2f[2] = tex->s1;t2f[3] = v[0];
2491 t2f[4] = tex->s1;t2f[5] = v[1];
2492 t2f[6] = tex->s2;t2f[7] = v[1];
2494 case PARTICLE_HBEAM:
2495 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2496 VectorSubtract(p->vel, p->org, up);
2497 VectorNormalize(up);
2498 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2499 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2500 t2f[0] = v[0];t2f[1] = tex->t1;
2501 t2f[2] = v[0];t2f[3] = tex->t2;
2502 t2f[4] = v[1];t2f[5] = tex->t2;
2503 t2f[6] = v[1];t2f[7] = tex->t1;
2508 // now render batches of particles based on blendmode and texture
2509 blendmode = PBLEND_INVALID;
2511 GL_LockArrays(0, numsurfaces*4);
2514 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2516 p = cl.particles + surfacelist[surfacelistindex];
2518 if (blendmode != p->blendmode)
2520 blendmode = p->blendmode;
2524 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2526 case PBLEND_INVALID:
2528 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2531 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2535 if (texture != particletexture[p->texnum].texture)
2537 texture = particletexture[p->texnum].texture;
2538 R_Mesh_TexBind(0, R_GetTexture(texture));
2541 // iterate until we find a change in settings
2542 batchstart = surfacelistindex++;
2543 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2545 p = cl.particles + surfacelist[surfacelistindex];
2546 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2550 batchcount = surfacelistindex - batchstart;
2551 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2553 GL_LockArrays(0, 0);
2556 void R_DrawParticles (void)
2559 int drawparticles = r_drawparticles.integer;
2560 float minparticledist;
2562 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2568 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2569 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2571 // LordHavoc: early out conditions
2572 if (!cl.num_particles)
2575 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2576 gravity = frametime * cl.movevars_gravity;
2577 dvel = 1+4*frametime;
2578 decalfade = frametime * 255 / cl_decals_fadetime.value;
2579 update = frametime > 0;
2580 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2581 drawdist2 = drawdist2*drawdist2;
2583 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2587 if (cl.free_particle > i)
2588 cl.free_particle = i;
2594 if (p->delayedspawn > cl.time)
2596 p->delayedspawn = 0;
2600 p->size += p->sizeincrease * frametime;
2601 p->alpha -= p->alphafade * frametime;
2603 if (p->alpha <= 0 || p->die <= cl.time)
2606 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2608 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2610 if (p->typeindex == pt_blood)
2611 p->size += frametime * 8;
2613 p->vel[2] -= p->gravity * gravity;
2614 f = 1.0f - min(p->liquidfriction * frametime, 1);
2615 VectorScale(p->vel, f, p->vel);
2619 p->vel[2] -= p->gravity * gravity;
2622 f = 1.0f - min(p->airfriction * frametime, 1);
2623 VectorScale(p->vel, f, p->vel);
2627 VectorCopy(p->org, oldorg);
2628 VectorMA(p->org, frametime, p->vel, p->org);
2629 if (p->bounce && cl.time >= p->delayedcollisions)
2631 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);
2632 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2633 // or if the trace hit something flagged as NOIMPACT
2634 // then remove the particle
2635 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2637 VectorCopy(trace.endpos, p->org);
2638 // react if the particle hit something
2639 if (trace.fraction < 1)
2641 VectorCopy(trace.endpos, p->org);
2643 if (p->staintexnum >= 0)
2645 // blood - splash on solid
2646 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2649 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2650 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2651 if (cl_decals.integer)
2653 // create a decal for the blood splat
2654 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!
2659 if (p->typeindex == pt_blood)
2661 // blood - splash on solid
2662 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2664 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2666 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)));
2667 if (cl_decals.integer)
2669 // create a decal for the blood splat
2670 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);
2675 else if (p->bounce < 0)
2677 // bounce -1 means remove on impact
2682 // anything else - bounce off solid
2683 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2684 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2685 if (DotProduct(p->vel, p->vel) < 0.03)
2686 VectorClear(p->vel);
2692 if (p->typeindex != pt_static)
2694 switch (p->typeindex)
2696 case pt_entityparticle:
2697 // particle that removes itself after one rendered frame
2704 a = CL_PointSuperContents(p->org);
2705 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2709 a = CL_PointSuperContents(p->org);
2710 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2714 a = CL_PointSuperContents(p->org);
2715 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2719 if (cl.time > p->time2)
2722 p->time2 = cl.time + (rand() & 3) * 0.1;
2723 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2724 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2726 a = CL_PointSuperContents(p->org);
2727 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2735 else if (p->delayedspawn)
2739 // don't render particles too close to the view (they chew fillrate)
2740 // also don't render particles behind the view (useless)
2741 // further checks to cull to the frustum would be too slow here
2742 switch(p->typeindex)
2745 // beams have no culling
2746 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2749 if(cl_particles_visculling.integer)
2750 if (!r_refdef.viewcache.world_novis)
2751 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2753 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2755 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2758 // anything else just has to be in front of the viewer and visible at this distance
2759 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2760 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2767 if (cl.free_particle > i)
2768 cl.free_particle = i;
2771 // reduce cl.num_particles if possible
2772 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2775 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2777 particle_t *oldparticles = cl.particles;
2778 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2779 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2780 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2781 Mem_Free(oldparticles);