2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "cl_collision.h"
27 #define ABSOLUTE_MAX_PARTICLES 1<<24 // upper limit on cl.max_particles
28 #define ABSOLUTE_MAX_DECALS 1<<24 // upper limit on cl.max_decals
30 // must match ptype_t values
31 particletype_t particletype[pt_total] =
33 {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen)
34 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic
35 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static
36 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark
37 {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam
38 {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain
39 {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal
40 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow
41 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble
42 {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood
43 {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke
44 {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal
45 {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle
48 #define PARTICLEEFFECT_UNDERWATER 1
49 #define PARTICLEEFFECT_NOTUNDERWATER 2
51 typedef struct particleeffectinfo_s
53 int effectnameindex; // which effect this belongs to
54 // PARTICLEEFFECT_* bits
56 // blood effects may spawn very few particles, so proper fraction-overflow
57 // handling is very important, this variable keeps track of the fraction
58 double particleaccumulator;
59 // the math is: countabsolute + requestedcount * countmultiplier * quality
60 // absolute number of particles to spawn, often used for decals
61 // (unaffected by quality and requestedcount)
63 // multiplier for the number of particles CL_ParticleEffect was told to
64 // spawn, most effects do not really have a count and hence use 1, so
65 // this is often the actual count to spawn, not merely a multiplier
66 float countmultiplier;
67 // if > 0 this causes the particle to spawn in an evenly spaced line from
68 // originmins to originmaxs (causing them to describe a trail, not a box)
70 // type of particle to spawn (defines some aspects of behavior)
72 // blending mode used on this particle type
74 // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc)
75 porientation_t orientation;
76 // range of colors to choose from in hex RRGGBB (like HTML color tags),
77 // randomly interpolated at spawn
78 unsigned int color[2];
79 // a random texture is chosen in this range (note the second value is one
80 // past the last choosable, so for example 8,16 chooses any from 8 up and
82 // if start and end of the range are the same, no randomization is done
84 // range of size values randomly chosen when spawning, plus size increase over time
86 // range of alpha values randomly chosen when spawning, plus alpha fade
88 // how long the particle should live (note it is also removed if alpha drops to 0)
90 // how much gravity affects this particle (negative makes it fly up!)
92 // how much bounce the particle has when it hits a surface
93 // if negative the particle is removed on impact
95 // if in air this friction is applied
96 // if negative the particle accelerates
98 // if in liquid (water/slime/lava) this friction is applied
99 // if negative the particle accelerates
100 float liquidfriction;
101 // these offsets are added to the values given to particleeffect(), and
102 // then an ellipsoid-shaped jitter is added as defined by these
103 // (they are the 3 radii)
105 // stretch velocity factor (used for sparks)
106 float originoffset[3];
107 float velocityoffset[3];
108 float originjitter[3];
109 float velocityjitter[3];
110 float velocitymultiplier;
111 // an effect can also spawn a dlight
112 float lightradiusstart;
113 float lightradiusfade;
116 qboolean lightshadow;
118 unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color!
121 particleeffectinfo_t;
123 #define MAX_PARTICLEEFFECTNAME 256
124 char particleeffectname[MAX_PARTICLEEFFECTNAME][64];
126 #define MAX_PARTICLEEFFECTINFO 4096
128 particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO];
130 static int particlepalette[256];
132 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7
133 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15
134 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23
135 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31
136 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39
137 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47
138 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55
139 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63
140 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71
141 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79
142 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87
143 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95
144 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103
145 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111
146 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119
147 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127
148 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135
149 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143
150 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151
151 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159
152 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167
153 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175
154 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183
155 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191
156 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199
157 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207
158 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215
159 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223
160 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231
161 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239
162 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247
163 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255
166 int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61};
167 int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66};
168 int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3};
170 //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff};
172 #define MAX_PARTICLETEXTURES 1024
173 // particletexture_t is a rectangle in the particlefonttexture
174 typedef struct particletexture_s
177 float s1, t1, s2, t2;
181 static rtexturepool_t *particletexturepool;
182 static rtexture_t *particlefonttexture;
183 static particletexture_t particletexture[MAX_PARTICLETEXTURES];
184 skinframe_t *decalskinframe;
186 // texture numbers in particle font
187 static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7};
188 static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15};
189 static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23};
190 static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31};
191 static const int tex_rainsplash = 32;
192 static const int tex_particle = 63;
193 static const int tex_bubble = 62;
194 static const int tex_raindrop = 61;
195 static const int tex_beam = 60;
197 cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"};
198 cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"};
199 cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"};
200 cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"};
201 cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"};
202 cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"};
203 cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood"};
204 cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"};
205 cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"};
206 cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"};
207 cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"};
208 cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"};
209 cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"};
210 cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"};
211 cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"};
212 cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"};
213 cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"};
214 cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"};
215 cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"};
216 cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"};
217 cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"};
218 cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"};
219 cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"};
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);
502 void CL_Particles_Shutdown (void)
506 // list of all 26 parameters:
507 // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file
508 // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color
509 // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle
510 // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM)
511 // palpha - opacity of particle as 0-255 (can be more than 255)
512 // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second)
513 // ptime - how long the particle can live (note it is also removed if alpha drops to nothing)
514 // pgravity - how much effect gravity has on the particle (0-1)
515 // 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
516 // px,py,pz - starting origin of particle
517 // pvx,pvy,pvz - starting velocity of particle
518 // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1)
519 // blendmode - one of the PBLEND_ values
520 // orientation - one of the PARTICLE_ values
521 // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide particle color (-1 to use none)
522 // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none)
523 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)
528 if (!cl_particles.integer)
530 for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++);
531 if (cl.free_particle >= cl.max_particles)
534 lifetime = palpha / min(1, palphafade);
535 part = &cl.particles[cl.free_particle++];
536 if (cl.num_particles < cl.free_particle)
537 cl.num_particles = cl.free_particle;
538 memset(part, 0, sizeof(*part));
539 part->typeindex = ptypeindex;
540 part->blendmode = blendmode;
541 if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM)
543 particletexture_t *tex = &particletexture[ptex];
544 if(tex->t1 == 0 && tex->t2 == 1) // full height of texture?
545 part->orientation = PARTICLE_VBEAM;
547 part->orientation = PARTICLE_HBEAM;
550 part->orientation = orientation;
551 l2 = (int)lhrandom(0.5, 256.5);
553 part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
554 part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
555 part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
556 part->staintexnum = staintex;
557 if(staincolor1 >= 0 && staincolor2 >= 0)
559 l2 = (int)lhrandom(0.5, 256.5);
561 if(blendmode == PBLEND_INVMOD)
563 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant
564 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000;
565 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000;
569 r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant
570 g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000;
571 b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000;
573 if(r > 0xFF) r = 0xFF;
574 if(g > 0xFF) g = 0xFF;
575 if(b > 0xFF) b = 0xFF;
579 r = part->color[0]; // -1 is shorthand for stain = particle color
583 part->staincolor = r * 65536 + g * 256 + b;
586 part->sizeincrease = psizeincrease;
587 part->alpha = palpha;
588 part->alphafade = palphafade;
589 part->gravity = pgravity;
590 part->bounce = pbounce;
591 part->stretch = stretch;
593 part->org[0] = px + originjitter * v[0];
594 part->org[1] = py + originjitter * v[1];
595 part->org[2] = pz + originjitter * v[2];
596 part->vel[0] = pvx + velocityjitter * v[0];
597 part->vel[1] = pvy + velocityjitter * v[1];
598 part->vel[2] = pvz + velocityjitter * v[2];
600 part->airfriction = pairfriction;
601 part->liquidfriction = pliquidfriction;
602 part->die = cl.time + lifetime;
603 part->delayedcollisions = 0;
604 part->qualityreduction = pqualityreduction;
605 // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance
606 if (part->typeindex == pt_rain)
610 float lifetime = part->die - cl.time;
613 // turn raindrop into simple spark and create delayedspawn splash effect
614 part->typeindex = pt_spark;
616 VectorMA(part->org, lifetime, part->vel, endvec);
617 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, true, false, NULL, false);
618 part->die = cl.time + lifetime * trace.fraction;
619 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);
622 part2->delayedspawn = part->die;
623 part2->die += part->die - cl.time;
624 for (i = rand() & 7;i < 10;i++)
626 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);
629 part2->delayedspawn = part->die;
630 part2->die += part->die - cl.time;
635 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow)
637 float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1);
640 VectorMA(part->org, lifetime, part->vel, endvec);
641 trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false);
642 part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1;
647 void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha)
651 if (!cl_decals.integer)
653 for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++);
654 if (cl.free_decal >= cl.max_decals)
656 decal = &cl.decals[cl.free_decal++];
657 if (cl.num_decals < cl.free_decal)
658 cl.num_decals = cl.free_decal;
659 memset(decal, 0, sizeof(*decal));
660 decal->typeindex = pt_decal;
661 decal->texnum = texnum;
662 VectorAdd(org, normal, decal->org);
663 VectorCopy(normal, decal->normal);
665 decal->alpha = alpha;
666 decal->time2 = cl.time;
667 l2 = (int)lhrandom(0.5, 256.5);
669 decal->color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF;
670 decal->color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF;
671 decal->color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF;
672 decal->owner = hitent;
673 decal->clusterindex = -1000; // no vis culling unless we're sure
676 // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0)
677 decal->ownermodel = cl.entities[decal->owner].render.model;
678 Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin);
679 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal);
683 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
685 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org);
687 decal->clusterindex = leaf->clusterindex;
692 void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2)
695 float bestfrac, bestorg[3], bestnormal[3];
697 int besthitent = 0, hitent;
700 for (i = 0;i < 32;i++)
703 VectorMA(org, maxdist, org2, org2);
704 trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, true, false, &hitent, false);
705 // take the closest trace result that doesn't end up hitting a NOMARKS
706 // surface (sky for example)
707 if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
709 bestfrac = trace.fraction;
711 VectorCopy(trace.endpos, bestorg);
712 VectorCopy(trace.plane.normal, bestnormal);
716 CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha);
719 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount);
720 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount);
721 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)
724 matrix4x4_t tempmatrix;
725 VectorLerp(originmins, 0.5, originmaxs, center);
726 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
727 if (effectnameindex == EFFECT_SVC_PARTICLE)
729 if (cl_particles.integer)
731 // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead
733 CL_ParticleExplosion(center);
734 else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225))
735 CL_ParticleEffect(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
738 count *= cl_particles_quality.value;
739 for (;count > 0;count--)
741 int k = particlepalette[(palettecolor & ~7) + (rand()&7)];
742 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);
747 else if (effectnameindex == EFFECT_TE_WIZSPIKE)
748 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20);
749 else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE)
750 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226);
751 else if (effectnameindex == EFFECT_TE_SPIKE)
753 if (cl_particles_bulletimpacts.integer)
755 if (cl_particles_quake.integer)
757 if (cl_particles_smoke.integer)
758 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
762 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
763 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
764 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);
768 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
769 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
771 else if (effectnameindex == EFFECT_TE_SPIKEQUAD)
773 if (cl_particles_bulletimpacts.integer)
775 if (cl_particles_quake.integer)
777 if (cl_particles_smoke.integer)
778 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
782 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
783 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count);
784 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);
788 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
789 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
790 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);
792 else if (effectnameindex == EFFECT_TE_SUPERSPIKE)
794 if (cl_particles_bulletimpacts.integer)
796 if (cl_particles_quake.integer)
798 if (cl_particles_smoke.integer)
799 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
803 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
804 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
805 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);
809 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
810 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
812 else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD)
814 if (cl_particles_bulletimpacts.integer)
816 if (cl_particles_quake.integer)
818 if (cl_particles_smoke.integer)
819 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
823 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count);
824 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count);
825 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);
829 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
830 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
831 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);
833 else if (effectnameindex == EFFECT_TE_BLOOD)
835 if (!cl_particles_blood.integer)
837 if (cl_particles_quake.integer)
838 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73);
841 static double bloodaccumulator = 0;
842 //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);
843 bloodaccumulator += count * 0.333 * cl_particles_quality.value;
844 for (;bloodaccumulator > 0;bloodaccumulator--)
845 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);
848 else if (effectnameindex == EFFECT_TE_SPARK)
849 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count);
850 else if (effectnameindex == EFFECT_TE_PLASMABURN)
852 // plasma scorch mark
853 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
854 CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
855 CL_AllocLightFlash(NULL, &tempmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
857 else if (effectnameindex == EFFECT_TE_GUNSHOT)
859 if (cl_particles_bulletimpacts.integer)
861 if (cl_particles_quake.integer)
862 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
865 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
866 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
867 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);
871 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
872 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
874 else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD)
876 if (cl_particles_bulletimpacts.integer)
878 if (cl_particles_quake.integer)
879 CL_ParticleEffect(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0);
882 CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count);
883 CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count);
884 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);
888 R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64);
889 CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
890 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);
892 else if (effectnameindex == EFFECT_TE_EXPLOSION)
894 CL_ParticleExplosion(center);
895 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);
897 else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD)
899 CL_ParticleExplosion(center);
900 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);
902 else if (effectnameindex == EFFECT_TE_TAREXPLOSION)
904 if (cl_particles_quake.integer)
907 for (i = 0;i < 1024 * cl_particles_quality.value;i++)
910 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);
912 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);
916 CL_ParticleExplosion(center);
917 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);
919 else if (effectnameindex == EFFECT_TE_SMALLFLASH)
920 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);
921 else if (effectnameindex == EFFECT_TE_FLAMEJET)
923 count *= cl_particles_quality.value;
925 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);
927 else if (effectnameindex == EFFECT_TE_LAVASPLASH)
929 float i, j, inc, vel;
932 inc = 8 / cl_particles_quality.value;
933 for (i = -128;i < 128;i += inc)
935 for (j = -128;j < 128;j += inc)
937 dir[0] = j + lhrandom(0, inc);
938 dir[1] = i + lhrandom(0, inc);
940 org[0] = center[0] + dir[0];
941 org[1] = center[1] + dir[1];
942 org[2] = center[2] + lhrandom(0, 64);
943 vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale
944 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);
948 else if (effectnameindex == EFFECT_TE_TELEPORT)
950 float i, j, k, inc, vel;
953 if (cl_particles_quake.integer)
954 inc = 4 / cl_particles_quality.value;
956 inc = 8 / cl_particles_quality.value;
957 for (i = -16;i < 16;i += inc)
959 for (j = -16;j < 16;j += inc)
961 for (k = -24;k < 32;k += inc)
963 VectorSet(dir, i*8, j*8, k*8);
964 VectorNormalize(dir);
965 vel = lhrandom(50, 113);
966 if (cl_particles_quake.integer)
967 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);
969 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);
973 if (!cl_particles_quake.integer)
974 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);
975 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);
977 else if (effectnameindex == EFFECT_TE_TEI_G3)
978 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);
979 else if (effectnameindex == EFFECT_TE_TEI_SMOKE)
981 if (cl_particles_smoke.integer)
983 count *= 0.25f * cl_particles_quality.value;
985 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);
988 else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION)
990 CL_ParticleExplosion(center);
991 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);
993 else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT)
996 R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64);
997 CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
998 if (cl_particles_smoke.integer)
999 for (f = 0;f < count;f += 4.0f / cl_particles_quality.value)
1000 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);
1001 if (cl_particles_sparks.integer)
1002 for (f = 0;f < count;f += 1.0f / cl_particles_quality.value)
1003 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);
1004 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);
1006 else if (effectnameindex == EFFECT_EF_FLAME)
1008 count *= 300 * cl_particles_quality.value;
1010 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);
1011 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);
1013 else if (effectnameindex == EFFECT_EF_STARDUST)
1015 count *= 200 * cl_particles_quality.value;
1017 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);
1018 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);
1020 else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3))
1024 int smoke, blood, bubbles, r, color;
1026 if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS)
1029 Vector4Set(light, 0, 0, 0, 0);
1031 if (effectnameindex == EFFECT_TR_ROCKET)
1032 Vector4Set(light, 3.0f, 1.5f, 0.5f, 200);
1033 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1035 if (gamemode == GAME_PRYDON && !cl_particles_quake.integer)
1036 Vector4Set(light, 0.3f, 0.6f, 1.2f, 100);
1038 Vector4Set(light, 1.2f, 0.5f, 1.0f, 200);
1040 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1041 Vector4Set(light, 0.75f, 1.5f, 3.0f, 200);
1045 matrix4x4_t tempmatrix;
1046 Matrix4x4_CreateFromQuakeEntity(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]);
1047 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);
1048 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1052 if (!spawnparticles)
1055 if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2])
1058 VectorSubtract(originmaxs, originmins, dir);
1059 len = VectorNormalizeLength(dir);
1062 dec = -ent->persistent.trail_time;
1063 ent->persistent.trail_time += len;
1064 if (ent->persistent.trail_time < 0.01f)
1067 // if we skip out, leave it reset
1068 ent->persistent.trail_time = 0.0f;
1073 // advance into this frame to reach the first puff location
1074 VectorMA(originmins, dec, dir, pos);
1077 smoke = cl_particles.integer && cl_particles_smoke.integer;
1078 blood = cl_particles.integer && cl_particles_blood.integer;
1079 bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME));
1080 qd = 1.0f / cl_particles_quality.value;
1087 if (effectnameindex == EFFECT_TR_BLOOD)
1089 if (cl_particles_quake.integer)
1091 color = particlepalette[67 + (rand()&3)];
1092 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);
1097 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);
1100 else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD)
1102 if (cl_particles_quake.integer)
1105 color = particlepalette[67 + (rand()&3)];
1106 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);
1111 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);
1117 if (effectnameindex == EFFECT_TR_ROCKET)
1119 if (cl_particles_quake.integer)
1122 color = particlepalette[ramp3[r]];
1123 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);
1127 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);
1128 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);
1131 else if (effectnameindex == EFFECT_TR_GRENADE)
1133 if (cl_particles_quake.integer)
1136 color = particlepalette[ramp3[r]];
1137 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);
1141 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);
1144 else if (effectnameindex == EFFECT_TR_WIZSPIKE)
1146 if (cl_particles_quake.integer)
1149 color = particlepalette[52 + (rand()&7)];
1150 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);
1151 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);
1153 else if (gamemode == GAME_GOODVSBAD2)
1156 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);
1160 color = particlepalette[20 + (rand()&7)];
1161 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);
1164 else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE)
1166 if (cl_particles_quake.integer)
1169 color = particlepalette[230 + (rand()&7)];
1170 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);
1171 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);
1175 color = particlepalette[226 + (rand()&7)];
1176 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);
1179 else if (effectnameindex == EFFECT_TR_VORESPIKE)
1181 if (cl_particles_quake.integer)
1183 color = particlepalette[152 + (rand()&3)];
1184 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);
1186 else if (gamemode == GAME_GOODVSBAD2)
1189 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);
1191 else if (gamemode == GAME_PRYDON)
1194 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);
1197 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);
1199 else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE)
1202 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);
1204 else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA)
1207 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);
1209 else if (effectnameindex == EFFECT_TR_GLOWTRAIL)
1210 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);
1214 if (effectnameindex == EFFECT_TR_ROCKET)
1215 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);
1216 else if (effectnameindex == EFFECT_TR_GRENADE)
1217 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);
1219 // advance to next time and position
1222 VectorMA (pos, dec, dir, pos);
1225 ent->persistent.trail_time = len;
1227 else if (developer.integer >= 1)
1228 Con_Printf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]);
1231 // this is also called on point effects with spawndlight = true and
1232 // spawnparticles = true
1233 // it is called CL_ParticleTrail because most code does not want to supply
1234 // these parameters, only trail handling does
1235 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)
1238 qboolean found = false;
1239 if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0])
1241 Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex);
1242 return; // no such effect
1244 VectorLerp(originmins, 0.5, originmaxs, center);
1245 if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex)
1247 int effectinfoindex;
1250 particleeffectinfo_t *info;
1252 vec3_t centervelocity;
1258 qboolean underwater;
1259 // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one
1260 VectorLerp(originmins, 0.5, originmaxs, center);
1261 VectorLerp(velocitymins, 0.5, velocitymaxs, centervelocity);
1262 supercontents = CL_PointSuperContents(center);
1263 underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0;
1264 VectorSubtract(originmaxs, originmins, traildir);
1265 traillen = VectorLength(traildir);
1266 VectorNormalize(traildir);
1267 for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++)
1269 if (info->effectnameindex == effectnameindex)
1272 if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater)
1274 if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater)
1277 // spawn a dlight if requested
1278 if (info->lightradiusstart > 0 && spawndlight)
1280 matrix4x4_t tempmatrix;
1281 if (info->trailspacing > 0)
1282 Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]);
1284 Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]);
1285 if (info->lighttime > 0 && info->lightradiusfade > 0)
1287 // light flash (explosion, etc)
1288 // called when effect starts
1289 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);
1294 // called by CL_LinkNetworkEntity
1295 Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1);
1296 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);
1297 r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
1301 if (!spawnparticles)
1306 if (info->tex[1] > info->tex[0])
1308 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1309 tex = min(tex, info->tex[1] - 1);
1311 if(info->staintex[0] < 0)
1312 staintex = info->staintex[0];
1315 staintex = (int)lhrandom(info->staintex[0], info->staintex[1]);
1316 staintex = min(staintex, info->staintex[1] - 1);
1318 if (info->particletype == pt_decal)
1319 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]);
1320 else if (info->orientation == PARTICLE_HBEAM)
1321 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);
1324 if (!cl_particles.integer)
1326 switch (info->particletype)
1328 case pt_smoke: if (!cl_particles_smoke.integer) continue;break;
1329 case pt_spark: if (!cl_particles_sparks.integer) continue;break;
1330 case pt_bubble: if (!cl_particles_bubbles.integer) continue;break;
1331 case pt_blood: if (!cl_particles_blood.integer) continue;break;
1332 case pt_rain: if (!cl_particles_rain.integer) continue;break;
1333 case pt_snow: if (!cl_particles_snow.integer) continue;break;
1336 VectorCopy(originmins, trailpos);
1337 if (info->trailspacing > 0)
1339 info->particleaccumulator += traillen / info->trailspacing * cl_particles_quality.value;
1340 trailstep = info->trailspacing / cl_particles_quality.value;
1344 info->particleaccumulator += info->countabsolute + pcount * info->countmultiplier * cl_particles_quality.value;
1347 info->particleaccumulator = bound(0, info->particleaccumulator, 16384);
1348 for (;info->particleaccumulator >= 1;info->particleaccumulator--)
1350 if (info->tex[1] > info->tex[0])
1352 tex = (int)lhrandom(info->tex[0], info->tex[1]);
1353 tex = min(tex, info->tex[1] - 1);
1357 trailpos[0] = lhrandom(originmins[0], originmaxs[0]);
1358 trailpos[1] = lhrandom(originmins[1], originmaxs[1]);
1359 trailpos[2] = lhrandom(originmins[2], originmaxs[2]);
1362 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);
1364 VectorMA(trailpos, trailstep, traildir, trailpos);
1371 CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles);
1374 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)
1376 CL_ParticleTrail(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true);
1384 void CL_EntityParticles (const entity_t *ent)
1387 float pitch, yaw, dist = 64, beamlength = 16, org[3], v[3];
1388 static vec3_t avelocities[NUMVERTEXNORMALS];
1389 if (!cl_particles.integer) return;
1390 if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused
1392 Matrix4x4_OriginFromMatrix(&ent->render.matrix, org);
1394 if (!avelocities[0][0])
1395 for (i = 0;i < NUMVERTEXNORMALS * 3;i++)
1396 avelocities[0][i] = lhrandom(0, 2.55);
1398 for (i = 0;i < NUMVERTEXNORMALS;i++)
1400 yaw = cl.time * avelocities[i][0];
1401 pitch = cl.time * avelocities[i][1];
1402 v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength;
1403 v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength;
1404 v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength;
1405 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);
1410 void CL_ReadPointFile_f (void)
1412 vec3_t org, leakorg;
1414 char *pointfile = NULL, *pointfilepos, *t, tchar;
1415 char name[MAX_OSPATH];
1420 FS_StripExtension (cl.worldmodel->name, name, sizeof (name));
1421 strlcat (name, ".pts", sizeof (name));
1422 pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL);
1425 Con_Printf("Could not open %s\n", name);
1429 Con_Printf("Reading %s...\n", name);
1430 VectorClear(leakorg);
1433 pointfilepos = pointfile;
1434 while (*pointfilepos)
1436 while (*pointfilepos == '\n' || *pointfilepos == '\r')
1441 while (*t && *t != '\n' && *t != '\r')
1445 #if _MSC_VER >= 1400
1446 #define sscanf sscanf_s
1448 r = sscanf (pointfilepos,"%f %f %f", &org[0], &org[1], &org[2]);
1454 VectorCopy(org, leakorg);
1457 if (cl.num_particles < cl.max_particles - 3)
1460 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);
1463 Mem_Free(pointfile);
1464 VectorCopy(leakorg, org);
1465 Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, org[0], org[1], org[2]);
1467 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);
1468 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);
1469 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);
1474 CL_ParseParticleEffect
1476 Parse an effect out of the server message
1479 void CL_ParseParticleEffect (void)
1482 int i, count, msgcount, color;
1484 MSG_ReadVector(org, cls.protocol);
1485 for (i=0 ; i<3 ; i++)
1486 dir[i] = MSG_ReadChar () * (1.0 / 16.0);
1487 msgcount = MSG_ReadByte ();
1488 color = MSG_ReadByte ();
1490 if (msgcount == 255)
1495 CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color);
1500 CL_ParticleExplosion
1504 void CL_ParticleExplosion (const vec3_t org)
1510 R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64);
1511 CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF);
1513 if (cl_particles_quake.integer)
1515 for (i = 0;i < 1024;i++)
1521 color = particlepalette[ramp1[r]];
1522 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);
1526 color = particlepalette[ramp2[r]];
1527 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);
1533 i = CL_PointSuperContents(org);
1534 if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER))
1536 if (cl_particles.integer && cl_particles_bubbles.integer)
1537 for (i = 0;i < 128 * cl_particles_quality.value;i++)
1538 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);
1542 if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer)
1544 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1548 for (k = 0;k < 16;k++)
1551 VectorMA(org, 128, v2, v);
1552 trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, true, false, NULL, false);
1553 if (trace.fraction >= 0.1)
1556 VectorSubtract(trace.endpos, org, v2);
1557 VectorScale(v2, 2.0f, v2);
1558 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);
1564 if (cl_particles_explosions_shell.integer)
1565 R_NewExplosion(org);
1570 CL_ParticleExplosion2
1574 void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength)
1577 if (!cl_particles.integer) return;
1579 for (i = 0;i < 512 * cl_particles_quality.value;i++)
1581 k = particlepalette[colorStart + (i % colorLength)];
1582 if (cl_particles_quake.integer)
1583 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);
1585 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);
1589 static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount)
1591 if (cl_particles_sparks.integer)
1593 sparkcount *= cl_particles_quality.value;
1594 while(sparkcount-- > 0)
1595 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);
1599 static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount)
1601 if (cl_particles_smoke.integer)
1603 smokecount *= cl_particles_quality.value;
1604 while(smokecount-- > 0)
1605 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);
1609 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)
1612 if (!cl_particles.integer) return;
1614 count = (int)(count * cl_particles_quality.value);
1617 k = particlepalette[colorbase + (rand()&3)];
1618 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);
1622 void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type)
1625 float minz, maxz, lifetime = 30;
1626 if (!cl_particles.integer) return;
1627 if (dir[2] < 0) // falling
1629 minz = maxs[2] + dir[2] * 0.1;
1632 lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]);
1637 maxz = maxs[2] + dir[2] * 0.1;
1639 lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]);
1642 count = (int)(count * cl_particles_quality.value);
1647 if (!cl_particles_rain.integer) break;
1648 count *= 4; // ick, this should be in the mod or maps?
1652 k = particlepalette[colorbase + (rand()&3)];
1653 if (gamemode == GAME_GOODVSBAD2)
1654 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);
1656 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);
1660 if (!cl_particles_snow.integer) break;
1663 k = particlepalette[colorbase + (rand()&3)];
1664 if (gamemode == GAME_GOODVSBAD2)
1665 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);
1667 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);
1671 Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type);
1675 static cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"};
1676 static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"};
1677 static cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"};
1678 static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"};
1680 #define PARTICLETEXTURESIZE 64
1681 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8)
1683 static unsigned char shadebubble(float dx, float dy, vec3_t light)
1687 dz = 1 - (dx*dx+dy*dy);
1688 if (dz > 0) // it does hit the sphere
1692 normal[0] = dx;normal[1] = dy;normal[2] = dz;
1693 VectorNormalize(normal);
1694 dot = DotProduct(normal, light);
1695 if (dot > 0.5) // interior reflection
1696 f += ((dot * 2) - 1);
1697 else if (dot < -0.5) // exterior reflection
1698 f += ((dot * -2) - 1);
1700 normal[0] = dx;normal[1] = dy;normal[2] = -dz;
1701 VectorNormalize(normal);
1702 dot = DotProduct(normal, light);
1703 if (dot > 0.5) // interior reflection
1704 f += ((dot * 2) - 1);
1705 else if (dot < -0.5) // exterior reflection
1706 f += ((dot * -2) - 1);
1708 f += 16; // just to give it a haze so you can see the outline
1709 f = bound(0, f, 255);
1710 return (unsigned char) f;
1716 int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols;
1717 void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height)
1719 *basex = (texnum % particlefontcols) * particlefontcellwidth;
1720 *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight;
1721 *width = particlefontcellwidth;
1722 *height = particlefontcellheight;
1725 static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata)
1727 int basex, basey, w, h, y;
1728 CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h);
1729 if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE)
1730 Sys_Error("invalid particle texture size for autogenerating");
1731 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1732 memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4);
1735 void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha)
1738 float cx, cy, dx, dy, f, iradius;
1740 cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1741 cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f;
1742 iradius = 1.0f / radius;
1743 alpha *= (1.0f / 255.0f);
1744 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1746 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1750 f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha;
1755 d = data + (y * PARTICLETEXTURESIZE + x) * 4;
1756 d[0] += (int)(f * (blue - d[0]));
1757 d[1] += (int)(f * (green - d[1]));
1758 d[2] += (int)(f * (red - d[2]));
1764 void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb)
1767 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1769 data[0] = bound(minb, data[0], maxb);
1770 data[1] = bound(ming, data[1], maxg);
1771 data[2] = bound(minr, data[2], maxr);
1775 void particletextureinvert(unsigned char *data)
1778 for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4)
1780 data[0] = 255 - data[0];
1781 data[1] = 255 - data[1];
1782 data[2] = 255 - data[2];
1786 // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC
1787 static void R_InitBloodTextures (unsigned char *particletexturedata)
1790 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1793 for (i = 0;i < 8;i++)
1795 memset(&data[0][0][0], 255, sizeof(data));
1796 for (k = 0;k < 24;k++)
1797 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 96, 0, 0, 160);
1798 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1799 particletextureinvert(&data[0][0][0]);
1800 setuptex(tex_bloodparticle[i], &data[0][0][0], particletexturedata);
1804 for (i = 0;i < 8;i++)
1806 memset(&data[0][0][0], 255, sizeof(data));
1808 for (j = 1;j < 10;j++)
1809 for (k = min(j, m - 1);k < m;k++)
1810 particletextureblotch(&data[0][0][0], (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8);
1811 //particletextureclamp(&data[0][0][0], 32, 32, 32, 255, 255, 255);
1812 particletextureinvert(&data[0][0][0]);
1813 setuptex(tex_blooddecal[i], &data[0][0][0], particletexturedata);
1818 //uncomment this to make engine save out particle font to a tga file when run
1819 //#define DUMPPARTICLEFONT
1821 static void R_InitParticleTexture (void)
1823 int x, y, d, i, k, m;
1824 int basex, basey, w, h;
1828 fs_offset_t filesize;
1830 // a note: decals need to modulate (multiply) the background color to
1831 // properly darken it (stain), and they need to be able to alpha fade,
1832 // this is a very difficult challenge because it means fading to white
1833 // (no change to background) rather than black (darkening everything
1834 // behind the whole decal polygon), and to accomplish this the texture is
1835 // inverted (dark red blood on white background becomes brilliant cyan
1836 // and white on black background) so we can alpha fade it to black, then
1837 // we invert it again during the blendfunc to make it work...
1839 #ifndef DUMPPARTICLEFONT
1840 decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, false);
1843 particlefonttexture = decalskinframe->base;
1844 // TODO maybe allow custom grid size?
1845 particlefontwidth = image_width;
1846 particlefontheight = image_height;
1847 particlefontcellwidth = image_width / 8;
1848 particlefontcellheight = image_height / 8;
1849 particlefontcols = 8;
1850 particlefontrows = 8;
1855 unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1856 unsigned char data[PARTICLETEXTURESIZE][PARTICLETEXTURESIZE][4];
1858 particlefontwidth = particlefontheight = PARTICLEFONTSIZE;
1859 particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE;
1860 particlefontcols = 8;
1861 particlefontrows = 8;
1863 memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4);
1866 for (i = 0;i < 8;i++)
1868 memset(&data[0][0][0], 255, sizeof(data));
1871 unsigned char noise1[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2], noise2[PARTICLETEXTURESIZE*2][PARTICLETEXTURESIZE*2];
1873 fractalnoise(&noise1[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8);
1874 fractalnoise(&noise2[0][0], PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4);
1876 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1878 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1879 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1881 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1882 d = (noise2[y][x] - 128) * 3 + 192;
1884 d = (int)(d * (1-(dx*dx+dy*dy)));
1885 d = (d * noise1[y][x]) >> 7;
1886 d = bound(0, d, 255);
1887 data[y][x][3] = (unsigned char) d;
1894 setuptex(tex_smoke[i], &data[0][0][0], particletexturedata);
1898 memset(&data[0][0][0], 255, sizeof(data));
1899 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1901 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1902 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1904 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1905 f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy)));
1906 data[y][x][3] = (int) (bound(0.0f, f, 255.0f));
1909 setuptex(tex_rainsplash, &data[0][0][0], particletexturedata);
1912 memset(&data[0][0][0], 255, sizeof(data));
1913 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1915 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1916 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1918 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1919 d = (int)(256 * (1 - (dx*dx+dy*dy)));
1920 d = bound(0, d, 255);
1921 data[y][x][3] = (unsigned char) d;
1924 setuptex(tex_particle, &data[0][0][0], particletexturedata);
1927 memset(&data[0][0][0], 255, sizeof(data));
1928 light[0] = 1;light[1] = 1;light[2] = 1;
1929 VectorNormalize(light);
1930 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1932 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1933 // stretch upper half of bubble by +50% and shrink lower half by -50%
1934 // (this gives an elongated teardrop shape)
1936 dy = (dy - 0.5f) * 2.0f;
1938 dy = (dy - 0.5f) / 1.5f;
1939 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1941 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1942 // shrink bubble width to half
1944 data[y][x][3] = shadebubble(dx, dy, light);
1947 setuptex(tex_raindrop, &data[0][0][0], particletexturedata);
1950 memset(&data[0][0][0], 255, sizeof(data));
1951 light[0] = 1;light[1] = 1;light[2] = 1;
1952 VectorNormalize(light);
1953 for (y = 0;y < PARTICLETEXTURESIZE;y++)
1955 dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1956 for (x = 0;x < PARTICLETEXTURESIZE;x++)
1958 dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1);
1959 data[y][x][3] = shadebubble(dx, dy, light);
1962 setuptex(tex_bubble, &data[0][0][0], particletexturedata);
1964 // Blood particles and blood decals
1965 R_InitBloodTextures (particletexturedata);
1968 for (i = 0;i < 8;i++)
1970 memset(&data[0][0][0], 255, sizeof(data));
1971 for (k = 0;k < 12;k++)
1972 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/16, 0, 0, 0, 128);
1973 for (k = 0;k < 3;k++)
1974 particletextureblotch(&data[0][0][0], PARTICLETEXTURESIZE/2, 0, 0, 0, 160);
1975 //particletextureclamp(&data[0][0][0], 64, 64, 64, 255, 255, 255);
1976 particletextureinvert(&data[0][0][0]);
1977 setuptex(tex_bulletdecal[i], &data[0][0][0], particletexturedata);
1980 #ifdef DUMPPARTICLEFONT
1981 Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata);
1984 decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE);
1985 particlefonttexture = decalskinframe->base;
1987 Mem_Free(particletexturedata);
1989 for (i = 0;i < MAX_PARTICLETEXTURES;i++)
1991 CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h);
1992 particletexture[i].texture = particlefonttexture;
1993 particletexture[i].s1 = (basex + 1) / (float)particlefontwidth;
1994 particletexture[i].t1 = (basey + 1) / (float)particlefontheight;
1995 particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth;
1996 particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight;
1999 #ifndef DUMPPARTICLEFONT
2000 particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, true);
2001 if (!particletexture[tex_beam].texture)
2004 unsigned char noise3[64][64], data2[64][16][4];
2006 fractalnoise(&noise3[0][0], 64, 4);
2008 for (y = 0;y < 64;y++)
2010 dy = (y - 0.5f*64) / (64*0.5f-1);
2011 for (x = 0;x < 16;x++)
2013 dx = (x - 0.5f*16) / (16*0.5f-2);
2014 d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]);
2015 data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255);
2016 data2[y][x][3] = 255;
2020 #ifdef DUMPPARTICLEFONT
2021 Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]);
2023 particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_PRECACHE | TEXF_FORCELINEAR, NULL);
2025 particletexture[tex_beam].s1 = 0;
2026 particletexture[tex_beam].t1 = 0;
2027 particletexture[tex_beam].s2 = 1;
2028 particletexture[tex_beam].t2 = 1;
2030 // now load an texcoord/texture override file
2031 buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize);
2038 if(!COM_ParseToken_Simple(&bufptr, true, false))
2040 if(!strcmp(com_token, "\n"))
2041 continue; // empty line
2042 i = atoi(com_token) % MAX_PARTICLETEXTURES;
2043 particletexture[i].texture = particlefonttexture;
2045 if (!COM_ParseToken_Simple(&bufptr, true, false))
2047 if (!strcmp(com_token, "\n"))
2049 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2052 particletexture[i].s1 = atof(com_token);
2054 if (!COM_ParseToken_Simple(&bufptr, true, false))
2056 if (!strcmp(com_token, "\n"))
2058 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2061 particletexture[i].t1 = atof(com_token);
2063 if (!COM_ParseToken_Simple(&bufptr, true, false))
2065 if (!strcmp(com_token, "\n"))
2067 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2070 particletexture[i].s2 = atof(com_token);
2072 if (!COM_ParseToken_Simple(&bufptr, true, false))
2074 if (!strcmp(com_token, "\n"))
2076 Con_Printf("particlefont file: syntax should be texnum texturename or texnum x y w h\n");
2079 particletexture[i].t2 = atof(com_token);
2085 static void r_part_start(void)
2088 // generate particlepalette for convenience from the main one
2089 for (i = 0;i < 256;i++)
2090 particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2];
2091 particletexturepool = R_AllocTexturePool();
2092 R_InitParticleTexture ();
2093 CL_Particles_LoadEffectInfo();
2096 static void r_part_shutdown(void)
2098 R_FreeTexturePool(&particletexturepool);
2101 static void r_part_newmap(void)
2104 R_SkinFrame_MarkUsed(decalskinframe);
2105 CL_Particles_LoadEffectInfo();
2108 #define BATCHSIZE 256
2109 unsigned short particle_elements[BATCHSIZE*6];
2111 void R_Particles_Init (void)
2114 for (i = 0;i < BATCHSIZE;i++)
2116 particle_elements[i*6+0] = i*4+0;
2117 particle_elements[i*6+1] = i*4+1;
2118 particle_elements[i*6+2] = i*4+2;
2119 particle_elements[i*6+3] = i*4+0;
2120 particle_elements[i*6+4] = i*4+2;
2121 particle_elements[i*6+5] = i*4+3;
2124 Cvar_RegisterVariable(&r_drawparticles);
2125 Cvar_RegisterVariable(&r_drawparticles_drawdistance);
2126 Cvar_RegisterVariable(&r_drawdecals);
2127 Cvar_RegisterVariable(&r_drawdecals_drawdistance);
2128 R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap);
2131 void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2133 int surfacelistindex;
2135 float *v3f, *t2f, *c4f;
2136 particletexture_t *tex;
2137 float right[3], up[3], size, ca;
2138 float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value * r_refdef.view.colorscale;
2139 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2141 RSurf_ActiveWorldEntity();
2143 r_refdef.stats.decals += numsurfaces;
2144 R_Mesh_ResetTextureState();
2145 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2146 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2147 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2148 R_SetupGenericShader(true);
2149 GL_DepthMask(false);
2150 GL_DepthRange(0, 1);
2151 GL_PolygonOffset(0, 0);
2153 GL_CullFace(GL_NONE);
2155 // generate all the vertices at once
2156 for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++)
2158 d = cl.decals + surfacelist[surfacelistindex];
2161 c4f = particle_color4f + 16*surfacelistindex;
2162 ca = d->alpha * alphascale;
2163 if (r_refdef.fogenabled)
2164 ca *= RSurf_FogVertex(d->org);
2165 Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1);
2166 Vector4Copy(c4f, c4f + 4);
2167 Vector4Copy(c4f, c4f + 8);
2168 Vector4Copy(c4f, c4f + 12);
2170 // calculate vertex positions
2171 size = d->size * cl_particles_size.value;
2172 VectorVectors(d->normal, right, up);
2173 VectorScale(right, size, right);
2174 VectorScale(up, size, up);
2175 v3f = particle_vertex3f + 12*surfacelistindex;
2176 v3f[ 0] = d->org[0] - right[0] - up[0];
2177 v3f[ 1] = d->org[1] - right[1] - up[1];
2178 v3f[ 2] = d->org[2] - right[2] - up[2];
2179 v3f[ 3] = d->org[0] - right[0] + up[0];
2180 v3f[ 4] = d->org[1] - right[1] + up[1];
2181 v3f[ 5] = d->org[2] - right[2] + up[2];
2182 v3f[ 6] = d->org[0] + right[0] + up[0];
2183 v3f[ 7] = d->org[1] + right[1] + up[1];
2184 v3f[ 8] = d->org[2] + right[2] + up[2];
2185 v3f[ 9] = d->org[0] + right[0] - up[0];
2186 v3f[10] = d->org[1] + right[1] - up[1];
2187 v3f[11] = d->org[2] + right[2] - up[2];
2189 // calculate texcoords
2190 tex = &particletexture[d->texnum];
2191 t2f = particle_texcoord2f + 8*surfacelistindex;
2192 t2f[0] = tex->s1;t2f[1] = tex->t2;
2193 t2f[2] = tex->s1;t2f[3] = tex->t1;
2194 t2f[4] = tex->s2;t2f[5] = tex->t1;
2195 t2f[6] = tex->s2;t2f[7] = tex->t2;
2198 // now render the decals all at once
2199 // (this assumes they all use one particle font texture!)
2200 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2201 R_Mesh_TexBind(0, R_GetTexture(particletexture[63].texture));
2202 GL_LockArrays(0, numsurfaces*4);
2203 R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, particle_elements, 0, 0);
2204 GL_LockArrays(0, 0);
2207 void R_DrawDecals (void)
2210 int drawdecals = r_drawdecals.integer;
2216 frametime = bound(0, cl.time - cl.decals_updatetime, 1);
2217 cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1);
2219 // LordHavoc: early out conditions
2223 decalfade = frametime * 256 / cl_decals_fadetime.value;
2224 drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality;
2225 drawdist2 = drawdist2*drawdist2;
2227 for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++)
2229 if (!decal->typeindex)
2232 if (cl.time > decal->time2 + cl_decals_time.value)
2234 decal->alpha -= decalfade;
2235 if (decal->alpha <= 0)
2241 if (cl.entities[decal->owner].render.model == decal->ownermodel)
2243 Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org);
2244 Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal);
2250 if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex))
2256 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))
2257 R_MeshQueue_AddTransparent(decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL);
2260 decal->typeindex = 0;
2261 if (cl.free_decal > i)
2265 // reduce cl.num_decals if possible
2266 while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0)
2269 if (cl.num_decals == cl.max_decals && cl.max_decals < ABSOLUTE_MAX_DECALS)
2271 decal_t *olddecals = cl.decals;
2272 cl.max_decals = min(cl.max_decals * 2, ABSOLUTE_MAX_DECALS);
2273 cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t));
2274 memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t));
2275 Mem_Free(olddecals);
2279 void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist)
2281 int surfacelistindex;
2282 int batchstart, batchcount;
2283 const particle_t *p;
2285 rtexture_t *texture;
2286 float *v3f, *t2f, *c4f;
2287 particletexture_t *tex;
2288 float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor;
2289 float ambient[3], diffuse[3], diffusenormal[3];
2290 vec4_t colormultiplier;
2291 float particle_vertex3f[BATCHSIZE*12], particle_texcoord2f[BATCHSIZE*8], particle_color4f[BATCHSIZE*16];
2293 RSurf_ActiveWorldEntity();
2295 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));
2297 r_refdef.stats.particles += numsurfaces;
2298 R_Mesh_ResetTextureState();
2299 R_Mesh_VertexPointer(particle_vertex3f, 0, 0);
2300 R_Mesh_TexCoordPointer(0, 2, particle_texcoord2f, 0, 0);
2301 R_Mesh_ColorPointer(particle_color4f, 0, 0);
2302 R_SetupGenericShader(true);
2303 GL_DepthMask(false);
2304 GL_DepthRange(0, 1);
2305 GL_PolygonOffset(0, 0);
2307 GL_CullFace(GL_NONE);
2309 // first generate all the vertices at once
2310 for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4)
2312 p = cl.particles + surfacelist[surfacelistindex];
2314 blendmode = p->blendmode;
2316 c4f[0] = p->color[0] * colormultiplier[0];
2317 c4f[1] = p->color[1] * colormultiplier[1];
2318 c4f[2] = p->color[2] * colormultiplier[2];
2319 c4f[3] = p->alpha * colormultiplier[3];
2322 case PBLEND_INVALID:
2325 // additive and modulate can just fade out in fog (this is correct)
2326 if (r_refdef.fogenabled)
2327 c4f[3] *= RSurf_FogVertex(p->org);
2328 // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures)
2335 // note: lighting is not cheap!
2336 if (particletype[p->typeindex].lighting)
2338 R_CompleteLightPoint(ambient, diffuse, diffusenormal, p->org, true);
2339 c4f[0] *= (ambient[0] + 0.5 * diffuse[0]);
2340 c4f[1] *= (ambient[1] + 0.5 * diffuse[1]);
2341 c4f[2] *= (ambient[2] + 0.5 * diffuse[2]);
2343 // mix in the fog color
2344 if (r_refdef.fogenabled)
2346 fog = RSurf_FogVertex(p->org);
2348 c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog;
2349 c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog;
2350 c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog;
2354 // copy the color into the other three vertices
2355 Vector4Copy(c4f, c4f + 4);
2356 Vector4Copy(c4f, c4f + 8);
2357 Vector4Copy(c4f, c4f + 12);
2359 size = p->size * cl_particles_size.value;
2360 tex = &particletexture[p->texnum];
2361 switch(p->orientation)
2363 case PARTICLE_INVALID:
2364 case PARTICLE_BILLBOARD:
2365 VectorScale(r_refdef.view.left, -size * p->stretch, right);
2366 VectorScale(r_refdef.view.up, size, up);
2367 v3f[ 0] = p->org[0] - right[0] - up[0];
2368 v3f[ 1] = p->org[1] - right[1] - up[1];
2369 v3f[ 2] = p->org[2] - right[2] - up[2];
2370 v3f[ 3] = p->org[0] - right[0] + up[0];
2371 v3f[ 4] = p->org[1] - right[1] + up[1];
2372 v3f[ 5] = p->org[2] - right[2] + up[2];
2373 v3f[ 6] = p->org[0] + right[0] + up[0];
2374 v3f[ 7] = p->org[1] + right[1] + up[1];
2375 v3f[ 8] = p->org[2] + right[2] + up[2];
2376 v3f[ 9] = p->org[0] + right[0] - up[0];
2377 v3f[10] = p->org[1] + right[1] - up[1];
2378 v3f[11] = p->org[2] + right[2] - up[2];
2379 t2f[0] = tex->s1;t2f[1] = tex->t2;
2380 t2f[2] = tex->s1;t2f[3] = tex->t1;
2381 t2f[4] = tex->s2;t2f[5] = tex->t1;
2382 t2f[6] = tex->s2;t2f[7] = tex->t2;
2384 case PARTICLE_ORIENTED_DOUBLESIDED:
2385 VectorVectors(p->vel, right, up);
2386 VectorScale(right, size * p->stretch, right);
2387 VectorScale(up, size, up);
2388 v3f[ 0] = p->org[0] - right[0] - up[0];
2389 v3f[ 1] = p->org[1] - right[1] - up[1];
2390 v3f[ 2] = p->org[2] - right[2] - up[2];
2391 v3f[ 3] = p->org[0] - right[0] + up[0];
2392 v3f[ 4] = p->org[1] - right[1] + up[1];
2393 v3f[ 5] = p->org[2] - right[2] + up[2];
2394 v3f[ 6] = p->org[0] + right[0] + up[0];
2395 v3f[ 7] = p->org[1] + right[1] + up[1];
2396 v3f[ 8] = p->org[2] + right[2] + up[2];
2397 v3f[ 9] = p->org[0] + right[0] - up[0];
2398 v3f[10] = p->org[1] + right[1] - up[1];
2399 v3f[11] = p->org[2] + right[2] - up[2];
2400 t2f[0] = tex->s1;t2f[1] = tex->t2;
2401 t2f[2] = tex->s1;t2f[3] = tex->t1;
2402 t2f[4] = tex->s2;t2f[5] = tex->t1;
2403 t2f[6] = tex->s2;t2f[7] = tex->t2;
2405 case PARTICLE_SPARK:
2406 len = VectorLength(p->vel);
2407 VectorNormalize2(p->vel, up);
2408 lenfactor = p->stretch * 0.04 * len;
2409 if(lenfactor < size * 0.5)
2410 lenfactor = size * 0.5;
2411 VectorMA(p->org, -lenfactor, up, v);
2412 VectorMA(p->org, lenfactor, up, up2);
2413 R_CalcBeam_Vertex3f(v3f, v, up2, size);
2414 t2f[0] = tex->s1;t2f[1] = tex->t2;
2415 t2f[2] = tex->s1;t2f[3] = tex->t1;
2416 t2f[4] = tex->s2;t2f[5] = tex->t1;
2417 t2f[6] = tex->s2;t2f[7] = tex->t2;
2419 case PARTICLE_VBEAM:
2420 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2421 VectorSubtract(p->vel, p->org, up);
2422 VectorNormalize(up);
2423 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2424 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2425 t2f[0] = tex->s2;t2f[1] = v[0];
2426 t2f[2] = tex->s1;t2f[3] = v[0];
2427 t2f[4] = tex->s1;t2f[5] = v[1];
2428 t2f[6] = tex->s2;t2f[7] = v[1];
2430 case PARTICLE_HBEAM:
2431 R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size);
2432 VectorSubtract(p->vel, p->org, up);
2433 VectorNormalize(up);
2434 v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch;
2435 v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch;
2436 t2f[0] = v[0];t2f[1] = tex->t1;
2437 t2f[2] = v[0];t2f[3] = tex->t2;
2438 t2f[4] = v[1];t2f[5] = tex->t2;
2439 t2f[6] = v[1];t2f[7] = tex->t1;
2444 // now render batches of particles based on blendmode and texture
2445 blendmode = PBLEND_INVALID;
2447 GL_LockArrays(0, numsurfaces*4);
2450 for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
2452 p = cl.particles + surfacelist[surfacelistindex];
2454 if (blendmode != p->blendmode)
2456 blendmode = p->blendmode;
2460 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2462 case PBLEND_INVALID:
2464 GL_BlendFunc(GL_SRC_ALPHA, GL_ONE);
2467 GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
2471 if (texture != particletexture[p->texnum].texture)
2473 texture = particletexture[p->texnum].texture;
2474 R_Mesh_TexBind(0, R_GetTexture(texture));
2477 // iterate until we find a change in settings
2478 batchstart = surfacelistindex++;
2479 for (;surfacelistindex < numsurfaces;surfacelistindex++)
2481 p = cl.particles + surfacelist[surfacelistindex];
2482 if (blendmode != p->blendmode || texture != particletexture[p->texnum].texture)
2486 batchcount = surfacelistindex - batchstart;
2487 R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, particle_elements, 0, 0);
2489 GL_LockArrays(0, 0);
2492 void R_DrawParticles (void)
2495 int drawparticles = r_drawparticles.integer;
2496 float minparticledist;
2498 float gravity, dvel, decalfade, frametime, f, dist, oldorg[3];
2504 frametime = bound(0, cl.time - cl.particles_updatetime, 1);
2505 cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1);
2507 // LordHavoc: early out conditions
2508 if (!cl.num_particles)
2511 minparticledist = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + 4.0f;
2512 gravity = frametime * cl.movevars_gravity;
2513 dvel = 1+4*frametime;
2514 decalfade = frametime * 255 / cl_decals_fadetime.value;
2515 update = frametime > 0;
2516 drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality;
2517 drawdist2 = drawdist2*drawdist2;
2519 for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++)
2523 if (cl.free_particle > i)
2524 cl.free_particle = i;
2530 if (p->delayedspawn > cl.time)
2532 p->delayedspawn = 0;
2536 p->size += p->sizeincrease * frametime;
2537 p->alpha -= p->alphafade * frametime;
2539 if (p->alpha <= 0 || p->die <= cl.time)
2542 if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0)
2544 if (p->liquidfriction && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK))
2546 if (p->typeindex == pt_blood)
2547 p->size += frametime * 8;
2549 p->vel[2] -= p->gravity * gravity;
2550 f = 1.0f - min(p->liquidfriction * frametime, 1);
2551 VectorScale(p->vel, f, p->vel);
2555 p->vel[2] -= p->gravity * gravity;
2558 f = 1.0f - min(p->airfriction * frametime, 1);
2559 VectorScale(p->vel, f, p->vel);
2563 VectorCopy(p->org, oldorg);
2564 VectorMA(p->org, frametime, p->vel, p->org);
2565 if (p->bounce && cl.time >= p->delayedcollisions)
2567 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);
2568 // if the trace started in or hit something of SUPERCONTENTS_NODROP
2569 // or if the trace hit something flagged as NOIMPACT
2570 // then remove the particle
2571 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID))
2573 VectorCopy(trace.endpos, p->org);
2574 // react if the particle hit something
2575 if (trace.fraction < 1)
2577 VectorCopy(trace.endpos, p->org);
2579 if (p->staintexnum >= 0)
2581 // blood - splash on solid
2582 if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS))
2585 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)),
2586 (p->staincolor >> 16) & 0xFF, (p->staincolor >> 8) & 0xFF, p->staincolor & 0xFF, (int)(p->alpha * p->size * (1.0f / 80.0f)));
2587 if (cl_decals.integer)
2589 // create a decal for the blood splat
2590 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!
2595 if (p->typeindex == pt_blood)
2597 // blood - splash on solid
2598 if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)
2600 if(p->staintexnum == -1) // staintex < -1 means no stains at all
2602 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)));
2603 if (cl_decals.integer)
2605 // create a decal for the blood splat
2606 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);
2611 else if (p->bounce < 0)
2613 // bounce -1 means remove on impact
2618 // anything else - bounce off solid
2619 dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce;
2620 VectorMA(p->vel, dist, trace.plane.normal, p->vel);
2621 if (DotProduct(p->vel, p->vel) < 0.03)
2622 VectorClear(p->vel);
2628 if (p->typeindex != pt_static)
2630 switch (p->typeindex)
2632 case pt_entityparticle:
2633 // particle that removes itself after one rendered frame
2640 a = CL_PointSuperContents(p->org);
2641 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP))
2645 a = CL_PointSuperContents(p->org);
2646 if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)))
2650 a = CL_PointSuperContents(p->org);
2651 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2655 if (cl.time > p->time2)
2658 p->time2 = cl.time + (rand() & 3) * 0.1;
2659 p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2660 p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32);
2662 a = CL_PointSuperContents(p->org);
2663 if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK))
2671 else if (p->delayedspawn)
2675 // don't render particles too close to the view (they chew fillrate)
2676 // also don't render particles behind the view (useless)
2677 // further checks to cull to the frustum would be too slow here
2678 switch(p->typeindex)
2681 // beams have no culling
2682 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2685 if(cl_particles_visculling.integer)
2686 if (!r_refdef.viewcache.world_novis)
2687 if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf)
2689 mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org);
2691 if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex))
2694 // anything else just has to be in front of the viewer and visible at this distance
2695 if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size))
2696 R_MeshQueue_AddTransparent(p->org, R_DrawParticle_TransparentCallback, NULL, i, NULL);
2703 if (cl.free_particle > i)
2704 cl.free_particle = i;
2707 // reduce cl.num_particles if possible
2708 while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0)
2711 if (cl.num_particles == cl.max_particles && cl.max_particles < ABSOLUTE_MAX_PARTICLES)
2713 particle_t *oldparticles = cl.particles;
2714 cl.max_particles = min(cl.max_particles * 2, ABSOLUTE_MAX_PARTICLES);
2715 cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t));
2716 memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t));
2717 Mem_Free(oldparticles);