//This file contains environmental effects for the designers #include "cg_local.h" #include "fx_public.h" extern void G_RadiusDamage ( vec3_t origin, gentity_t *attacker, float damage, float radius, gentity_t *ignore, int mod); /* ====================== CG_Spark Creates a spark effect ====================== */ void CG_Spark( vec3_t origin, vec3_t normal ) { FXTrail *particle; vec3_t dir, direction, start, end; vec3_t velocity, accel; float scale; int numSparks; AngleVectors( normal, normal, NULL, NULL ); for ( int j = 0; j < 3; j ++ ) normal[j] = normal[j] + (0.15f * crandom()); VectorNormalize( normal ); //cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgi_S_RegisterSound( va( "sound/world/ric%d.wav", (rand() & 2)+1) ) ); numSparks = 8 + (random() * 4.0f); scale = 0.2f + (random() *0.4); VectorMA( origin, 24.0f + (crandom() * 4.0f), normal, end ); //One long spark FX_AddLine( origin, end, 1.0f, scale, 0.0f, 1.0f, 0.25f, 125.0f, cgs.media.sparkShader ); for ( int i = 0; i < numSparks; i++ ) { scale = 0.2f + (random() *0.4); for ( j = 0; j < 3; j ++ ) dir[j] = normal[j] + (0.25f * crandom()); VectorNormalize(dir); VectorMA( origin, 0.0f + ( random() * 2.0f ), dir, start ); VectorMA( start, 2.0f + ( random() * 16.0f ), dir, end ); FX_AddLine( start, end, 1.0f, scale, 0.0f, 1.0f, 0.25f, 125.0f, cgs.media.sparkShader ); } if ( rand() & 1 ) { numSparks = 1 + (random() * 2.0f); for ( i = 0; i < numSparks; i++ ) { scale = 0.5f + (random() * 0.5f); VectorScale( normal, 250, velocity ); VectorSet( accel, 0, 0, -512 ); particle = FX_AddTrail( start, velocity, accel, 8.0f, -32.0f, scale, -scale, 1.0f, 0.5f, 0.25f, 700.0f, cgs.media.sparkShader, FXF_BOUNCE ); } } VectorMA( origin, 1, normal, direction ); scale = 6.0f + (random() * 8.0f); float alpha = 0.1 + (random() * 0.4f); VectorSet( velocity, 0, 0, 8 ); FX_AddSprite( direction, velocity, NULL, scale, scale, alpha, 0.0f, random()*45.0f, 0.0f, 1000.0f, cgs.media.steamShader ); } //------------------------------------------------------------------------------ void SteamThink( vec3_t position, vec3_t dest, vec3_t dir, vec3_t puff_scale ) { float speed = 200; vec3_t direction, facing, color = { 1.0f, 1.0f, 1.0f }; vec3_t velocity = { 0, 0, 128 }; vec3_t acceleration = { 0, 0, 256 }; float scale, dscale, len, detail; detail = FX_DetailLevel( position, 16, 1024 ); if (detail == 0) return; VectorSubtract( dest, position, facing ); len = VectorNormalize( facing ); VectorCopy( dir, direction ); if ( puff_scale[1] ) { VectorSet( color, 0.9f + random() * 0.1f, 0.8f + random() * 0.2f, 0.4f + random() * 0.3f ); } direction[0] += (2.0f + (crandom() * 2.0f)); direction[1] += (2.0f + (crandom() * 2.0f)); direction[2] += (2.0f + (crandom() * 2.0f)); AngleVectors( direction, direction, NULL, NULL ); VectorScale( direction, speed, velocity ); VectorScale( velocity, -1.0f, acceleration ); scale = 8.0f + (random() * 4.0f) * (puff_scale[0] / 10.0f); dscale = scale * 8.0 * (puff_scale[0] / 10.0f); FX_AddSprite( position, velocity, acceleration, scale, dscale, 1.0f, 0.0f, color, color, random() * 360, 0.25f, (len / speed) * 1000, cgs.media.steamShader ); } //void SteamJetThink( vec3_t position, vec3_t dest, vec3_t dir, vec3_t user ) //{ // FX_AddSpawner( position, dest, dir, NULL, 50, 0, 200 + ( random() * 500 ), (void *) SteamThink ); //} /* ====================== CG_Steam Creates a steam effect ====================== */ void CG_Steam( vec3_t position, vec3_t dest, vec3_t dir, float scale, int flags ) { vec3_t user; // stuff some extra info into the user field user[0] = scale; user[1] = (flags & 4); if ( (flags & 2) ) { FX_AddSpawner( position, dest, dir, user, 50, 0, 200 + ( random() * 500 ), (void *) SteamThink ); } else { SteamThink( position, dest, dir, user ); } } /* ====================== CG_CookingSteam Creates a basic cooking steam effect ====================== */ void CG_CookingSteam( vec3_t origin, float radius ) { vec3_t dir; VectorSet( dir, crandom()*2, crandom()*2, crandom() + radius); // always move mostly up VectorScale( dir, random() * 5 + 2, dir ); FX_AddSprite( origin, dir, NULL, radius, radius * 2, 0.4F, 0.0, 0, 0, 1000, cgs.media.steamShader ); } /* ====================== CG_Bolt Creates a electricity bolt effect ====================== */ #define DATA_EFFECTS 0 #define DATA_CHAOS 1 #define DATA_RADIUS 2 void BoltFireback( vec3_t start, vec3_t end, vec3_t velocity, vec3_t user ) { FX_AddElectricity( start, end, 1.0, user[DATA_RADIUS], 5.0, 1.0, 0.0, 200, cgs.media.bolt2Shader, (int)user[DATA_EFFECTS], user[DATA_CHAOS] ); if ( rand() & 1 ) FX_AddElectricity( end, start, 1.0, user[DATA_RADIUS] * 2, 5.0, 1.0, 0.0, 200, cgs.media.bolt2Shader, (int)user[DATA_EFFECTS], user[DATA_CHAOS] ); } //----------------------------- void BorgBoltFireback( vec3_t start, vec3_t end, vec3_t velocity, vec3_t user ) { FX_AddElectricity( start, end, 1.0, user[DATA_RADIUS], 5.0, 1.0, 0.0, 100, cgs.media.borgLightningShaders[2], (int)user[DATA_EFFECTS], user[DATA_CHAOS] ); if ( rand() & 1 ) FX_AddElectricity( end, start, 1.0, user[DATA_RADIUS] * 2, 5.0, 1.0, 0.0, 100, cgs.media.borgLightningShaders[3], (int)user[DATA_EFFECTS], user[DATA_CHAOS] ); } //----------------------------- void BlackAndWhiteBoltFireback( vec3_t start, vec3_t end, vec3_t velocity, vec3_t user ) { FX_AddElectricity( start, end, 1.0, user[DATA_RADIUS], 5.0, 1.0, 0.0, 100, cgs.media.whiteLaserShader, (int)user[DATA_EFFECTS], user[DATA_CHAOS] ); if ( rand() & 1 ) FX_AddElectricity( end, start, 1.0, user[DATA_RADIUS] * 2, 5.0, 1.0, 0.0, 100, cgs.media.whiteLaserShader, (int)user[DATA_EFFECTS], user[DATA_CHAOS] ); } //----------------------------- // This looks a bit cheesy void BoltSparkSpew( vec3_t origin, vec3_t dir, qhandle_t shader ) { FXTrail *particle; // Create the sparks for ( int i = 0; i < 6; i++ ) { particle = FX_AddTrail( origin, NULL, NULL, 2.0f + random() * 3, -5.0f, 0.5, -2.0, 0.3f, 0.0f, 0.25f, 500.0f, shader ); if ( particle == NULL ) return; FXE_Spray( dir, 25, 25, 0.6f, 300 + (rand() & 100), (FXPrimitive *) particle ); } } //----------------------------- void CG_Bolt( centity_t *cent ) { qboolean borg, bw; // bw = black and white int effects; float chaos, radius; // Set up all of the parms borg = (cent->gent->spawnflags & 8); bw = (cent->gent->spawnflags & 64); effects = (cent->gent->spawnflags & 16) ? FXF_TAPER : 0; effects = (cent->gent->spawnflags & 32) ? (FXF_WRAP | effects) : effects; chaos = cent->gent->random; radius = cent->gent->radius; // Delayed bolt that should "work" a while if ( cent->gent->spawnflags & 2 ) { vec3_t data; // This sucks, but the spawn function needs some extra bits of info data[DATA_EFFECTS] = effects; data[DATA_CHAOS] = chaos; data[DATA_RADIUS] = radius; if ( bw ) { FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, data, 70, random() * 25, 450, (void *) BlackAndWhiteBoltFireback, NULL, 768 ); } else if ( borg ) FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, data, 70, random() * 25, 450, (void *) BorgBoltFireback, NULL, 768 ); else FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, data, 70, random() * 25, 450, (void *) BoltFireback, NULL, 768 ); } else { // Continuous bolts if ( bw ) { FX_AddElectricity( cent->lerpOrigin, cent->currentState.origin2, 1.0, radius, 5.0, 1.0, 0.0, 200, cgs.media.whiteLaserShader, effects, chaos ); if ( rand() & 1 ) FX_AddElectricity( cent->currentState.origin2, cent->lerpOrigin, 1.0, radius * 2, 5.0, 1.0, 0.0, 200, cgs.media.whiteLaserShader, effects, chaos ); } else if ( borg ) { FX_AddElectricity( cent->lerpOrigin, cent->currentState.origin2, 1.0, radius, 5.0, 1.0, 0.0, 200, cgs.media.borgLightningShaders[2], effects, chaos ); if ( rand() & 1 ) FX_AddElectricity( cent->currentState.origin2, cent->lerpOrigin, 1.0, radius * 2, 5.0, 1.0, 0.0, 200, cgs.media.borgLightningShaders[3], effects, chaos ); } else { FX_AddElectricity( cent->lerpOrigin, cent->currentState.origin2, 1.0, radius, 5.0, 1.0, 0.0, 200, cgs.media.bolt2Shader, effects, chaos ); if ( rand() & 1 ) FX_AddElectricity( cent->currentState.origin2, cent->lerpOrigin, 1.0, radius * 2, 5.0, 1.0, 0.0, 200, cgs.media.bolt2Shader, effects, chaos ); } } // Bolt that generates sparks at the impact point if ( cent->gent->spawnflags & 4 ) { vec3_t dir; VectorSubtract( cent->lerpOrigin, cent->currentState.origin2, dir ); VectorNormalize( dir ); if ( bw ) { BoltSparkSpew( cent->currentState.origin2, dir, cgs.media.waterDropShader ); } else if ( borg ) BoltSparkSpew( cent->currentState.origin2, dir, cgs.media.borgFlareShader ); else BoltSparkSpew( cent->currentState.origin2, dir, cgs.media.spark2Shader ); } } /* ====================== CG_ForgeBolt Creates an orange electricity bolt effect with a pulse that travels down the beam ====================== */ void ForgeBoltFireback( vec3_t start, vec3_t end, vec3_t velocity, vec3_t user ) { FX_AddElectricity( start, end, 1.0, user[DATA_RADIUS], 5.0, 1.0, 0.0, 200, cgs.media.pjBoltShader, (int)user[DATA_EFFECTS], user[DATA_CHAOS] ); } //--------------------------------------------------- bool ForgeBoltPulse( FXPrimitive *fx, centity_t *ent ) { vec3_t origin, new_org; trace_t trace; qboolean remove = qfalse; VectorCopy( fx->m_origin, origin ); fx->UpdateOrigin(); VectorCopy( fx->m_origin, new_org ); CG_Trace( &trace, origin, NULL, NULL, new_org, -1, CONTENTS_SOLID ); if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid ) { // The effect hit something, presumably a barrier, so kill it remove = qtrue; return false; } vec3_t normal, rgb1 ={ 1.0F, 0.5F, 0.4F}, rgb2 ={ 1.0F, 1.0F, 0.3F};//, org; FXCylinder *fxc; // Convert the direction of travel in to a normal; VectorCopy( fx->m_velocity, normal ); VectorNormalize( normal ); VectorScale( normal, -1, normal ); fxc = FX_AddCylinder( new_org, normal, 16, 0, 16 - random() * 8, 0, 32 + random() * 24, 0, 0.2F, 0.2F, rgb1, rgb1, 1, cgs.media.psychicShader, 0.6F ); if ( fxc == NULL ) return false; fxc->SetFlags( FXF_WRAP ); fxc->SetSTScale( Q_irand(1,3) ); fxc = FX_AddCylinder( new_org, normal, 8, 0, 12 - random() * 8, 0, 24 + random() * 24, 0, 0.2F, 0.2F, rgb2, rgb2, 1, cgs.media.psychicShader, 0.6F ); if ( fxc == NULL ) return false; fxc->SetFlags( FXF_WRAP ); fxc->SetSTScale( Q_irand(1,2) ); return true; } //----------------------------- void CG_ForgeBolt( centity_t *cent ) { qboolean pulse; int effects; float chaos, radius; // Set up all of the parms pulse = (cent->gent->spawnflags & 8) ? qtrue : qfalse; effects = (cent->gent->spawnflags & 16) ? FXF_TAPER : 0; effects = (cent->gent->spawnflags & 32) ? (FXF_WRAP | effects) : effects; chaos = cent->gent->random; radius = cent->gent->radius; // Delayed bolt that should "work" a while if ( cent->gent->spawnflags & 2 ) { vec3_t data; // This sucks, but the spawn function needs some extra bits of info data[DATA_EFFECTS] = effects; data[DATA_CHAOS] = chaos; data[DATA_RADIUS] = radius; FX_AddSpawner( cent->lerpOrigin, cent->currentState.origin2, NULL, data, 70, random() * 25, 450, (void *) ForgeBoltFireback ); } else { FX_AddElectricity( cent->lerpOrigin, cent->currentState.origin2, 1.0, radius, 5.0, 1.0, 0.0, 200, cgs.media.pjBoltShader, effects, chaos ); if ( rand() & 1 ) FX_AddElectricity( cent->currentState.origin2, cent->lerpOrigin, 1.0, radius * 2, 5.0, 1.0, 0.0, 200, cgs.media.pjBoltShader, effects, chaos ); if ( cg.time > cent->gent->delay && pulse ) { vec3_t dir; float amt; VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, dir ); VectorNormalize( dir ); amt = 200 + random() * 100; VectorScale( dir, amt, dir ); FX_AddParticle( cent, cent->lerpOrigin, dir, NULL, 16, 0.0, 1.0, 1.0, 0.0, 0.0, 6000, cgs.media.ltblueParticleShader, FXF_NODRAW, ForgeBoltPulse ); cent->gent->delay = cg.time + 500; } } // Bolt that generates sparks at the impact point if ( cent->gent->spawnflags & 4 ) { vec3_t dir; VectorSubtract( cent->lerpOrigin, cent->currentState.origin2, dir ); VectorNormalize( dir ); BoltSparkSpew( cent->currentState.origin2, dir, cgs.media.dkorangeParticleShader ); } } /* ====================== CG_PsychoJism Creates a psycho jism bolt effect ====================== */ void CG_PsychoJism( vec3_t start, vec3_t end, qboolean open ) { vec3_t dir, rev_dir, angles, pos1, pos2, ofdir, ofang; float len, detail; int i; detail = FX_DetailLevel( start, 16, 1024 ); //Technically this needs to find the average or closer... // don't draw if we are too far away if (detail == 0 || cg_freezeFX.integer) { return; } VectorSubtract( end, start, dir ); len = VectorNormalize( dir ); VectorScale( dir, -1, rev_dir ); vectoangles( rev_dir, angles ); if(open) { // Calculate a new start VectorCopy( dir, ofdir ); vectoangles( ofdir, ofang ); ofang[2] = crandom() * 360; AngleVectors( ofang, NULL, ofdir, NULL ); VectorMA( start, len * 0.06f, ofdir, pos1 ); // Calculate a new end VectorMA( end, len * 0.08f, rev_dir, pos2 ); for ( i=0; i < 3; i++ ) { pos2[i] += crandom() * 2.0f; } } else { VectorCopy( start, pos1 ); VectorCopy( end, pos2 ); } FX_AddElectricity( pos1, pos2, 1.0f, 2.0f, 2.0f, 1.0f, 0.0f, 150.0f, cgs.media.pjBoltShader ); if(!open) { VectorMA( start, -4, dir, pos1 ); VectorMA( end, 4, dir, pos2 ); FX_AddLine( pos2, pos1, 1.0, 2.0, 32.0 * random() + 24.0 , 0.5f, 0.0f, 100.0f, cgs.media.spark3Shader ); CG_Spark(end, angles); } } /* =========================== Plasma Create directed and scaled plasma jet =========================== */ void CG_Plasma( vec3_t start, vec3_t end, vec4_t startRGBA, vec4_t endRGBA ) { vec3_t v, sp, sRGB, eRGB; float detail, len, salpha, ealpha; detail = FX_DetailLevel( start, 16, 1200 ); if ( detail == 0 ) return; salpha = Vector4to3( startRGBA, sRGB ); ealpha = Vector4to3( endRGBA, eRGB ); // Orient the plasma VectorSubtract( end, start, v ); len = VectorNormalize( v ); VectorMA( start, 0.5f, v, sp ); // Stash a quad at the base to make the effect look a bit more solid FX_AddQuad( sp, v, NULL, NULL, len * 0.36f, 0.0f, salpha, salpha, sRGB, sRGB, 0.0f, 45.0f, 0.0f, 200, cgs.media.prifleImpactShader ); // Add a subtle, random flutter to the cone direction v[0] += crandom() * 0.04; v[1] += crandom() * 0.04; v[2] += crandom() * 0.04; // Wanted the effect to be scalable based on the length of the jet. FX_AddCylinder( start, v, len * 0.05, len * 2.0f, len * 0.16f, len * 0.32f, len * 0.40f, len * 0.64f, salpha, ealpha, sRGB, eRGB, 200, cgs.media.plasmaShader, 0.3f ); FX_AddCylinder( start, v, len * 0.05, len * 4.0f, len * 0.16f, len * 0.32f, len * 0.28f, len * 0.64f, salpha, ealpha, sRGB, eRGB, 200, cgs.media.plasmaShader, 0.2f ); FX_AddCylinder( start, v, len * 0.25, len * 8.0f, len * 0.20f, len * 0.32f, len * 0.02f, len * 0.32f, salpha, ealpha, sRGB, eRGB, 200, cgs.media.plasmaShader, 0.1f ); } /* =========================== Drip Create timed drip effect =========================== */ bool DripCallback( FXPrimitive *fx, centity_t *ent ) { vec3_t org, dir = {0,0,-1}; fx->UpdateOrigin(); fx->UpdateAlpha(); fx->UpdateRGB(); VectorMA( fx->m_origin, -1.4, dir, org ); FX_AddLine( fx->m_origin, org, 1.0, fx->m_scale * 0.75, -(fx->m_scale * 4), fx->m_alpha, 0.0, fx->m_RGB, fx->m_RGB, 25, fx->m_shader ); return true; } //------------------------------------------------------------------------------ void DripSplash( vec3_t origin, vec3_t normal, vec3_t work, vec3_t user ) { FXTrail *particle; float detail; detail = FX_DetailLevel( origin, 16, 400 ); if (detail == 0) return; for ( int i = 0; i < 5; i++ ) { particle = FX_AddTrail( origin, NULL, NULL, 1.5f, -2.0f, work[0], -work[0], work[1], 0, user, user, 0.25f, 350.0f, work[2], rand() & FXF_BOUNCE ); if ( particle == NULL ) return; FXE_Spray( normal, 60, 50, 0.4f, 512, (FXPrimitive *) particle ); } switch( rand() & 3 ) { case 1: cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AMBIENT, cgi_S_RegisterSound( "sound/ambience/waterdrop1.wav") ); break; case 2: cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AMBIENT, cgi_S_RegisterSound( "sound/ambience/waterdrop2.wav") ); break; default: cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AMBIENT, cgi_S_RegisterSound( "sound/ambience/waterdrop3.wav") ); break; } } #define DRIP_ACCEL 370.0f //------------------------------------------------------------------------------ void CG_Drip( vec3_t origin, vec4_t RGBA, float diameter, int thinktime, qhandle_t shader ) { FXSpawner *spawner; trace_t trace; vec3_t vel, accel, end, RGB, work; float grow, alpha, time, dis; alpha = Vector4to3( RGBA, RGB ); // Calc how fast it should grow to reach drop size in the given amount of time grow = diameter / (float)((thinktime - cg.time) * 0.001); // Growing drop -- never moves FX_AddSprite( origin, NULL, NULL, 0.0, grow, alpha, alpha, RGB, RGB, 0.0, 0.0, thinktime - cg.time + cg.frametime, shader, 0 ); // Ideally, zero should be used for vel...so just use something sufficiently close VectorSet( vel, 0, 0, -0.00001f ); VectorSet( accel, 0, 0, -DRIP_ACCEL ); // Find out where it will hit VectorMA( origin, 4, accel, end ); gi.trace( &trace, origin, NULL, NULL, end, -1, MASK_SHOT );//ignore if ( trace.fraction < 1.0 ) { VectorSubtract( trace.endpos, origin, end ); dis = VectorNormalize( end ); time = sqrt( 2 / DRIP_ACCEL * dis ) * 1000; // Calculate how long the thing will take to travel that distance // Falling drop FX_AddParticle( NULL, origin, vel, accel, diameter, 0.0, alpha, alpha, RGB, RGB, 0.0, 0.0, time, shader, 0, DripCallback ); // Ok, so this is a little bit harsh, but all of this info needs to get to the spawner VectorSet( work, diameter, alpha, shader ); spawner = FX_AddSpawner( trace.endpos, trace.plane.normal, work, RGB, time, 0, time + 100, FXF_DELAY_SPAWN | FXF_SPAWN_ONCE, DripSplash, NULL ); } else // Falling a long way so just send one that will fall for 2 secs, but don't spawn a splash { FX_AddSprite( origin, vel, accel, diameter, 0.0, alpha, alpha, RGB, RGB, 0, 0, 2000, shader ); } } /* ====================== CG_ElectricFire Creates an electric fire effect ====================== */ void CG_ElectricFire( vec3_t origin, vec3_t normal ) { FXTrail *particle; vec3_t dir, direction, start, end; vec3_t velocity, accel; float scale, alpha; int numSparks; AngleVectors( normal, normal, NULL, NULL); numSparks = 4 + (random() * 8.0f); for ( int i = 0; i < numSparks; i++ ) { scale = 0.3f + (random() *0.4); for ( int j = 0; j < 3; j ++ ) dir[j] = normal[j] + (0.4f * crandom()); VectorNormalize(dir); VectorMA( origin, -1.0f + ( random() * 2.0f ), dir, start ); VectorMA( start, 2.0f + ( random() * 12.0f ), dir, end ); FX_AddLine( start, end, 1.0f, scale, 0.0f, 1.0f, 0.0f, 75.0f, cgs.media.sparkShader ); } scale = 0.5f + (random() * 0.5f); VectorScale( normal, 300, velocity ); VectorSet( accel, 0, 0, -600 ); particle = FX_AddTrail( start, velocity, accel, 6.0f, -24.0f, scale, -scale, 1.0f, 0.5f, 0.0f, 200.0f, cgs.media.sparkShader, FXF_BOUNCE ); if ( particle == NULL ) return; FXE_Spray( dir, 200, 200, 0.2f, 300, (FXPrimitive *) particle ); VectorMA( origin, 1, normal, direction ); VectorSet( velocity, 0, 0, 8 ); for ( i = 0; i < 3; i++) { scale = 6.0f + (random() * 8.0f); alpha = 0.1 + (random() * 0.4f); FX_AddSprite( direction, velocity, NULL, scale, scale, alpha, 0.0, random()*45.0f, 0.0f, 1000.0f, cgs.media.steamShader ); VectorMA( velocity, 9.0, normal, velocity); } } /* ====================== CG_Teleporter persistent teleporter fx for STASIS level ====================== */ bool teleporter_think( FXPrimitive *fx, centity_t *ent ) { vec3_t angvect; // Can generically update these . . even if the entity gets yanked. fx->UpdateAlpha(); fx->UpdateScale(); fx->UpdateRGB(); // If the ent was somehow removed, it would be meaningless to continue any further. if ( !ent ) return false; // Update the angle of the particle (position on the ellipsoid). VectorMA( fx->m_velocity, cg.frametime * 0.001, fx->m_acceleration, fx->m_velocity ); // Update the actual position of the particle. AngleVectors( fx->m_velocity, angvect, NULL, NULL ); fx->m_origin[0] = (4 * cos(cg.time * 0.004) + 20) * angvect[0] + ent->lerpOrigin[0]; fx->m_origin[1] = (4 * sin(cg.time * 0.005) + 20) * angvect[1] + ent->lerpOrigin[1]; fx->m_origin[2] = (4 * sin(cg.time * 0.006) + 30) * angvect[2] + ent->lerpOrigin[2]; return true; } //------------------------------------------------------------------------------ void CG_Teleporter( centity_t *cent ) { vec3_t vel, accel, angvect, org; // Velocity is actually the position on the sphere vel[ROLL] = 0; vel[YAW] = random() * 360.0; vel[PITCH] = random() * 360.0; // Accel is actually the velocity around the sphere accel[ROLL] = 0; accel[YAW] = crandom() * 180; accel[PITCH] = crandom() * 45; AngleVectors(vel, angvect, NULL, NULL); // Set the particle position org[0] = (4 * cos(cg.time * 0.004) + 20) * angvect[0] + cent->lerpOrigin[0]; org[1] = (4 * sin(cg.time * 0.005) + 20) * angvect[1] + cent->lerpOrigin[1]; org[2] = (4 * sin(cg.time * 0.006) + 30) * angvect[2] + cent->lerpOrigin[2]; // Use a couple of different kinds to break up the monotony if ( rand() & 1 ) { FX_AddParticle( cent, org, vel, accel, 1.0, 0.6f, 1.0, 0.3f, 0.0, 0.0, 6000, cgs.media.ltblueParticleShader, 0, teleporter_think ); } else { FX_AddParticle( cent, org, vel, accel, 1.0, 0.6f, 0.7f, 0.3f, 0.0, 0.0, 6000, cgs.media.purpleParticleShader, 0, teleporter_think ); } } /* ====================== CG_ParticleStream particle stream fx for STASIS level ====================== */ bool particle_stream_think( FXPrimitive *fx, centity_t *ent ) { vec3_t old_org; // Make it flicker. . .always safe to do this fx->m_scale = random() * 12 + 2; fx->m_alpha = random() * 0.4 + 0.6; // If the ent was somehow removed, we don't want to continue any further. if ( !ent ) return false; // Stash the old position so that we can draw a trailer line VectorCopy( fx->m_origin, old_org ); // Update the position of the particle. fx->m_origin[0] = cos(cg.time * 0.01 + fx->m_velocity[0]) * fx->m_velocity[1] + ent->lerpOrigin[0]; fx->m_origin[1] = sin(cg.time * 0.01 + fx->m_velocity[0]) * fx->m_velocity[1] + ent->lerpOrigin[1]; fx->m_origin[2] += (fx->m_velocity[2] * cg.frametime * 0.001); FX_AddLine( fx->m_origin, old_org, 1.0f, 2.0f, -4.0f, 0.6f, 0.0, 500, cgs.media.IMOD2Shader ); return true; } //------------------------------------------------------------------------------ void CG_ParticleStream( centity_t *cent ) { vec3_t vel, org, dir; float len, time; // This effect will currently only travel directly up or down--never sideways VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, dir ); len = VectorNormalize( dir ); // since the movement direction is limited, use the velocity var a bit more efficiently vel[0] = random() * 360; // random position around the cylinder vel[1] = random() > 0.9 ? 20 : 6; // random radius vel[2] = dir[2] * 120 + dir[2] * random() * 50; // random velocity (up or down) // Set the particle position org[0] = cos(cg.time * 0.01 + vel[0]) * vel[1] + cent->lerpOrigin[0]; org[1] = sin(cg.time * 0.01 + vel[0]) * vel[1] + cent->lerpOrigin[1]; org[2] = cent->lerpOrigin[2]; // Calculate how long the thing should live based on it's velocity and the distance it has to travel time = len / vel[2] * 1000; // Use a couple of different kinds to break up the monotony if ( rand() & 1 ) { FX_AddParticle( cent, org, vel, NULL, 16, 0.0, 1.0, 1.0, 0.0, 0.0, time, cgs.media.ltblueParticleShader, 0, particle_stream_think ); } else { FX_AddParticle( cent, org, vel, NULL, 16, 0.0, 1.0, 1.0, 0.0, 0.0, time, cgs.media.purpleParticleShader, 0, particle_stream_think ); } } /* ====================== CG_ElectricalExplosion Electrical Explosion ====================== */ void smoke_puffs( vec3_t position, vec3_t dest, vec3_t dir, vec3_t user ) { vec3_t direc; direc[0] = crandom() * 7; direc[1] = crandom() * 7; direc[2] = random() * 6 + 8; FX_AddSprite( position, direc, NULL, 6.0f, 10.0f, 0.3f, 0.0f, 0.0f, 0.0f, 2200, cgs.media.steamShader ); } //------------------------------------------------------------------------------ void electric_spark( vec3_t pos, vec3_t normal, vec3_t dir, vec3_t user ) { CG_Spark( pos, normal ); } //------------------------------------------------------------------------------ void CG_ElectricalExplosion( vec3_t start, vec3_t dir, float radius ) { localEntity_t *le; FXTrail *particle; vec3_t pos, temp, angles; int i, numSparks; float scale, dscale; // Spawn some delayed smoke FX_AddSpawner( start, dir, NULL, NULL, 150, 40, 9000, smoke_puffs ); vectoangles( dir, angles ); FX_AddSpawner( start, angles, NULL, NULL, 900, 800, 4000, FXF_DELAY_SPAWN, electric_spark ); // Create the sparks for the explosion numSparks = 46 + (random() * 8.0f); for ( i = 0; i < numSparks; i++ ) { scale = 0.2f + random(); dscale = -scale*2; particle = FX_AddTrail( start, NULL, NULL, 8.0f + random() * 6, -16.0f, scale, -scale, 1.0f, 0.0f, 0.25f, 700.0f, cgs.media.spark2Shader ); if ( particle == NULL ) return; FXE_Spray( dir, 200, 200, 0.3f, 500 + (rand() & 300), (FXPrimitive *) particle ); } // Create some initial smoke puffs for (i = 0; i < 12; i++) { VectorCopy( dir, temp ); temp[0] += crandom() * 0.5f; temp[1] += crandom() * 0.5f; temp[2] += crandom() * 0.5f; VectorMA( start, random() * 16 + 8, temp, pos ); VectorScale( temp, random() * 4 + 5, temp ); FX_AddSprite( pos, temp, NULL, 16.0, 3.0f, 1.0f, 0.0f, 0.0f, 0.0f, 2700 + random() * 600, cgs.media.steamShader ); } // Now place a cool explosion model on top VectorSubtract( cg.refdef.vieworg, start, dir ); VectorNormalize( dir ); le = CG_MakeExplosion( start, dir, cgs.media.explosionModel, 6, cgs.media.electricalExplosionSlowShader, 500, qfalse, radius * 0.01f + ( crandom() * 0.3f) ); le->light = 150; le->refEntity.renderfx |= RF_NOSHADOW; VectorSet( le->lightColor, 0.8f, 0.8f, 1.0f ); } /* =========================== Laser Create directed laser shot =========================== */ void CG_SmallSpark( vec3_t origin, vec3_t normal ) { vec3_t dir, direction, start, end, velocity; float scale; int numSparks; AngleVectors( normal, normal, NULL, NULL ); for ( int j = 0; j < 3; j ++ ) normal[j] = normal[j] + (0.1f * crandom()); VectorNormalize( normal ); numSparks = 6 + (random() * 4.0f ); for ( int i = 0; i < numSparks; i++ ) { scale = 0.1f + (random() *0.2f ); for ( j = 0; j < 3; j ++ ) dir[j] = normal[j] + (0.7f * crandom()); VectorMA( origin, 0.0f + ( random() * 0.5f ), dir, start ); VectorMA( start, 1.0f + ( random() * 1.5f ), dir, end ); FX_AddLine( start, end, 1.0f, scale, 0.0f, 1.0f, 0.7f, 4.0f, cgs.media.sparkShader ); } VectorMA( origin, 1, normal, direction ); scale = 2.0f + (random() * 3.0f ); float alpha = 0.6f + (random() * 0.4f ); VectorSet( velocity, crandom() * 2, crandom() * 2, 8 + random() * 4 ); VectorMA( velocity, 5, normal, velocity ); FX_AddSprite( direction, velocity, NULL, scale, scale, alpha, 0.0f, random() * 45.0f, 0.0f, 1000.0f, cgs.media.steamShader ); } //------------------------------------------------------------------------------ void CG_FireLaser( vec3_t start, vec3_t end, vec3_t normal, vec4_t laserRGB, qboolean hit_ent ) { vec3_t dir, right, up, angles, work, pos, sRGB, lRGB; float scale = 1.0f, alpha; int life = 0; if ( !(FX_DetailLevel( start, 16, 1200 ) )) return; // Orient the laser spray VectorSubtract( end, start, dir ); VectorNormalize( dir ); alpha = Vector4to3( laserRGB, lRGB ); VectorMA( end, 0.5f, normal, pos ); MakeNormalVectors( normal, right, up ); VectorSet( sRGB, 1.0f, 0.8f, 0.8f ); FX_AddSprite( start, NULL, NULL, 1.75f, 1.0f, alpha, 0.0f, lRGB, lRGB, 0.0f, 0.0f, 200, cgs.media.waterDropShader ); FX_AddLine( start, end, 1.0f, 3.0f, 5.0f, alpha, 0.0f, lRGB, lRGB, 125, cgs.media.whiteLaserShader ); FX_AddLine( start, end, 1.0f, 0.3f, 5.0f, random() * 0.4 + 0.4, 0.1f, 125, cgs.media.whiteLaserShader ); // Doing all of this extra stuff would look weird if it hits a player ent. if ( !hit_ent ) { FX_AddQuad( pos, normal, NULL, NULL, 3.5f, 1.0f, alpha, 0.0f, lRGB, lRGB, 0.0f, 0.0f, 0.0f, 200, cgs.media.waterDropShader ); for ( int t=0; t < 2; t ++ ) { VectorMA( pos, crandom() * 0.5f, right, work ); VectorMA( work, crandom() * 0.5f, up, work ); scale = crandom() * 0.5f + 1.75f; life = crandom() * 300 + 2100; VectorSet( sRGB, 1.0f, 0.7f, 0.2f ); FX_AddQuad( work, normal, NULL, NULL, scale, -0.1f, 1.0f, 0.0f, sRGB, sRGB, 0, 0, 0, life, cgs.media.waterDropShader ); } FX_AddQuad( pos, normal, NULL, NULL, scale * 2.5f, 0.0f, 1.0f, 0.0f, 0, 0, 0, life * 2, cgs.media.smokeShader ); vectoangles( normal, angles ); CG_SmallSpark( end, angles ); } else { // However, do add a little smoke puff FX_AddSprite( pos, NULL, NULL, 2.0f, 1.0f, alpha, 0.0f, lRGB, lRGB, 0.0f, 0.0f, 200, cgs.media.waterDropShader ); VectorMA( end, 1, normal, dir ); scale = 1.0f + (random() * 3.0f); CG_Smoke( dir, normal, scale, 12.0f, cgs.media.steamShader ); } } //------------------------------------------------------------------------------ void CG_AimLaser( vec3_t start, vec3_t end, vec3_t normal ) { vec3_t lRGB = {1.0,0.0,0.0}; // Beam FX_AddLine( start, end, 1.0f, 5.5f, 5.0f, random() * 0.2 + 0.2, 0.1f, lRGB, lRGB, 150, cgs.media.whiteLaserShader ); FX_AddLine( start, end, 1.0f, 0.3f, 5.0f, random() * 0.4 + 0.4, 0.1f, 125, cgs.media.whiteLaserShader ); // Flare at the start point FX_AddSprite( start, NULL, NULL, 1.5 + random() * 4, 0.0, 0.1f,0.0, 0.0, 0.0, 100, cgs.media.borgEyeFlareShader ); // endpoint flare FX_AddSprite( end, NULL, NULL, 2.5 + random() * 4, 0.0, 0.1f,0.0, 0.0, 0.0, 100, cgs.media.borgEyeFlareShader ); // oriented impact flare FX_AddQuad( end, normal, NULL, NULL, 1.5 + random() * 2, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 120, cgs.media.borgEyeFlareShader ); } /* ====================== CG_TransporterStream particle stream fx for forge level The particles will accelerate up to the half-way point of the cylinder, then deccelerate til they hit their target ====================== */ void CG_TransporterStream( centity_t *cent ) { vec3_t vel, accel, dir, pos, right, up; float len, time, acceleration, scale, dis, vf; VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, dir ); len = VectorNormalize( dir ); MakeNormalVectors( dir, right, up ); for ( int t=0; t < 3; t++ ) { // Create start offset within a circular radius VectorMA( cent->lerpOrigin, 8 * crandom(), right, pos ); VectorMA( pos, 8 * crandom(), up, pos ); acceleration = 80 + random() * 50; VectorScale( dir, acceleration, accel ); // acceleration vector VectorScale( dir, 0.0001, vel ); // Ideally, vel would be zero, so just make it really small dis = ( len * 0.8f ); // the two segs will be overlapping to cover up the middle // This is derived from dis = (vi)(t) + (1/2)(a)(t)^2 where the inital velocity (vi) = zero time = sqrt( 2 / acceleration * dis ); // Calculate how long the thing will take to travel that distance scale = 1.5f + random() * 4; // These will spawn at the base and accelerate towards the middle if ( rand() & 1 ) { FX_AddSprite( pos, vel, accel, scale, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, time * 1000, cgs.media.orangeParticleShader ); } else { FX_AddSprite( pos, vel, accel, scale, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, time * 1000, cgs.media.dkorangeParticleShader ); } // These will be spawned somewhere in the middle and deccelerate till they reach the end of their target VectorMA( pos, len - dis, dir, pos ); VectorScale( accel, -1, accel ); vf = sqrt( 2 * dis * acceleration ); // calculate the how fast it would be moving at the end of its path VectorScale( dir, vf, vel ); // this will be the _initial_ velocity for those starting in the middle if ( rand() & 1 ) { FX_AddSprite( pos, vel, accel, scale, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, time * 1000, cgs.media.orangeParticleShader ); } else { FX_AddSprite( pos, vel, accel, scale, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, time * 1000, cgs.media.dkorangeParticleShader ); } } } /* ------------------------- CG_Smoke ------------------------- */ void CG_Smoke( vec3_t origin, vec3_t dir, float radius, float speed, qhandle_t shader, int flags) { vec3_t velocity, accel; for ( int i = 0; i < 3; i++ ) { velocity[i] = dir[i] + ( 0.2f * crandom()); } VectorScale( velocity, speed, velocity ); VectorScale( velocity, -0.25f, accel ); accel[2] = random() * 12.0f + 6.0f; FX_AddSprite( origin, velocity, accel, radius + (crandom() * radius * 0.5f ), radius + (crandom() * radius), 0.9f + crandom(), 0.0f, 16.0f + random() * 45.0f, 0.5f, 2000, shader, flags ); } /* ------------------------- CG_ExplosionTrail ------------------------- */ bool explosionTrailThink( FXPrimitive *fx, centity_t *ent ) { localEntity_t *le=0; vec3_t direction, origin, new_org, angles, dir; trace_t trace; float scale; int i; qboolean remove = qfalse; VectorCopy( fx->m_origin, origin ); fx->UpdateOrigin(); VectorCopy( fx->m_origin, new_org ); CG_Trace( &trace, origin, NULL, NULL, new_org, -1, CONTENTS_SOLID ); if ( trace.fraction < 1.0f && !trace.startsolid && !trace.allsolid ) { // The effect hit something, presumably a barrier, so kill it // When the effect gets killed like this, it dies quickly and looks a bit thin. // Maybe something else should be done as well... remove = qtrue; //FIXME: FX_RemoveEffect( fx ); return false; } scale = 80 * 0.03f; VectorSubtract( new_org, origin, dir ); VectorNormalize( dir ); vectoangles( dir, angles ); //Orient the explosions to face the camera VectorSubtract( cg.refdef.vieworg, origin, direction ); VectorNormalize( direction ); for ( i = 0; i < 3 + (int)remove * 6; i++) { angles[2] = crandom() * 360; AngleVectors( angles, NULL, dir, NULL ); VectorMA( origin, random() * 50.0f, dir, new_org ); le = CG_MakeExplosion( new_org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 400 + (int)remove * 800, qfalse, random() * 1.0 + 0.8 );//random() * 1.0 + 1.0 ); } le->light = 150; VectorSet( le->lightColor, 64, 192, 255 ); //Shake the camera and damage everything in an area CG_ExplosionEffects( origin, 3.0f, 600 ); G_RadiusDamage( origin, ent->gent, 150, 80, NULL, MOD_UNKNOWN ); return true; } //------------------------------------------------------------------------------ void CG_ExplosionTrail( centity_t *cent ) { vec3_t dir; float len; VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, dir ); len = VectorNormalize( dir ); VectorScale( dir, 325, dir ); FX_AddParticle( cent, cent->lerpOrigin, dir, NULL, 16, 0.0, 1.0, 1.0, 0.0, 0.0, 6000, cgs.media.ltblueParticleShader, FXF_NODRAW, explosionTrailThink ); } /* ---------------------- CG_BorgEnergyBeam A scanning type beam ---------------------- */ void CG_BorgEnergyBeam( centity_t *cent ) { vec3_t normal, angles, base, dir, dir2, rgb; float len, alpha; VectorSubtract( cent->currentState.origin2, cent->lerpOrigin, normal ); len = VectorNormalize( normal ); vectoangles( normal, angles ); alpha = Vector4to3( cent->gent->startRGBA, rgb ); /* // Code to make the thing "snap" when it's doing the beam slices if ( abs( cent->gent->pos2[0] ) >= cent->gent->radius ) { // Snap back to start and move to the next slice cent->gent->pos2[0] = cent->gent->radius; cent->gent->pos2[1] -= ( cg.frametime * 0.0003 * cent->gent->speed ); } // The slice has moved past the end so snap back to the first slice position if ( abs( cent->gent->pos2[1] ) >= cent->gent->radius ) { cent->gent->pos2[1] = cent->gent->radius; } // Always move across the slice cent->gent->pos2[0] -= ( cg.frametime * 0.001 * cent->gent->speed ); */ if ( cent->gent->spawnflags & 2 ) { // Trace a cone angles[2] = cent->gent->angle; } AngleVectors( angles, NULL, dir, dir2 ); if ( cent->gent->spawnflags & 2 ) { // Cone VectorMA( cent->currentState.origin2, cent->gent->radius, dir, base ); } else { // Swinging pendulum VectorMA( cent->currentState.origin2, cent->gent->radius * ( sin( cent->gent->angle * 0.03f )), dir, base ); VectorMA( base, cent->gent->radius * ( cos( cent->gent->angle * 0.003f )), dir2, base ); // Do "snapping" beam slices // VectorMA( cent->currentState.origin2, cent->gent->pos2[0], dir, base ); // VectorMA( base, cent->gent->pos2[1], dir2, base ); } // Main trace laser FX_AddLine( cent->lerpOrigin, base, 64, 0.8f, 5.0f, alpha, 0.0, rgb, rgb, 120, cgs.media.whiteLaserShader ); // Faint trail at base. Is this really adding anything useful? FX_AddLine( cent->gent->pos1, base, 1, 1.0, 2.0, alpha * 0.2, 0.0, rgb, rgb, 1000, cgs.media.whiteLaserShader ); // Faint trace cone, adds a bit of meat to the effect FX_AddTri( cent->lerpOrigin, cent->gent->pos1, base, alpha * 0.2, 0.0, rgb, rgb, 800, cgs.media.solidWhiteShader ); // Laser impact point FX_AddSprite( base, NULL, NULL, random() * 2, 0.0, alpha, 0.0, rgb, rgb, 0.0, 0.0, 100, cgs.media.waterDropShader ); VectorCopy( base, cent->gent->pos1 ); cent->gent->angle += cent->gent->speed * 0.08f; } /* ---------------------- CG_ShimmeryThing Creates column or cone of shimmering lines Kind of looks like a teleporter effect ---------------------- */ void CG_ShimmeryThing( vec3_t start, vec3_t end, float radius, qboolean taper ) { vec3_t normal, angles, base, top, dir; float len; VectorSubtract( end, start, normal ); len = VectorNormalize( normal ); vectoangles( normal, angles ); for ( int i=0; i < 2; i++) { // Spawn the shards of light around a cylinder angles[2] = crandom() * 360; AngleVectors( angles, NULL, dir, NULL ); // See if the effect should be tapered at the top if ( taper ) { VectorMA( start, radius * 0.25f, dir, top ); } else { VectorMA( start, radius, dir, top ); } VectorMA( end, radius, dir, base ); // Use a couple of different kinds to break up the monotony.. if ( rand() & 1 ) { FX_AddLine( top, base, 1.0f, len * 0.008f, len * 0.19f, 0.3f, 0.0f, random() * 200 + 600, cgs.media.ltblueParticleShader ); } else { FX_AddLine( top, base, 1.0f, len * 0.008f, len * 0.19f, 0.2f, 0.0f, random() * 200 + 600, cgs.media.spark2Shader ); } } } /* ------------------------- CG_ShimmeryThing_Spawner ------------------------- */ void CG_Shimmer( vec3_t position, vec3_t dest, vec3_t dir, vec3_t other ) { CG_ShimmeryThing( position, dest, other[0], (qboolean) other[1] ); } void CG_ShimmeryThing_Spawner( vec3_t start, vec3_t end, float radius, qboolean taper, int duration ) { vec3_t packed = { radius, (float) taper, 0 }; FX_AddSpawner( start, end, NULL, packed, 100, 0, duration, (void *) CG_Shimmer, NULL, 512 ); } /* ---------------------- CG_Borg_Bolt Yellow bolts that spark when the endpoints get close together ---------------------- */ void CG_Borg_Bolt( centity_t *cent ) { vec3_t diff, neworg, start, end; float len; if (!cent->gent->enemy){ return;//we lost him } VectorCopy( cent->gent->enemy->currentOrigin, end ); if ( cent->gent->target2 ) { VectorCopy( cent->gent->chain->currentOrigin, start ); } else { VectorCopy( cent->lerpOrigin, start ); } // Get the midpoint of the seg VectorSubtract( end, start, diff ); len = VectorNormalize( diff ); VectorMA( start, len * 0.5, diff, neworg ); // If the length is pretty short, then spawn a glow spark if ( len > 0 && len < 12 ) { int ct; vec3_t angles, dir; FXTrail *particle; FX_AddSprite( neworg, NULL, NULL, random() * (128 / len) + 12, 16.0, 0.6f, 0.0, 0.0, 0.0, 300, cgs.media.yellowParticleShader ); vectoangles( dir, angles ); ct = 12 - len; // fun sparks for ( int t=0; t < ct; t++ ) { angles[1] = random() * 360; AngleVectors( angles, dir, NULL, NULL ); dir[2] = random() * 0.3f; particle = FX_AddTrail( neworg, NULL, NULL, 8.0f + random() * 6, -16.0f, 1, -1, 1.0f, 0.0f, 0.25f, 700.0f, cgs.media.yellowParticleShader ); if ( particle == NULL ) return; FXE_Spray( dir, 100, 150, 0.5f, 300 + (rand() & 300), (FXPrimitive *) particle ); } // If it's really short, spark and make a noise. Tried this without the if (len>0... and it was way // too obnoxious if ( len <= 5 ) { cgi_S_StartSound( neworg, 0, 0, cgi_S_RegisterSound( "sound/enemies/borg/borgtaser.wav") ); } } // Use this to scale down the width of the bolts. Otherwise, they will look pretty fairly nasty when they // get too short. len = len / 32; FX_AddElectricity( start, end, 1.0, len, 5.0, 1.0, 0.0, 200, cgs.media.yellowBoltShader ); if ( rand() & 1 ) { FX_AddElectricity( end, start, 1.0, len, 5.0, 1.0, 0.0, 200, cgs.media.yellowBoltShader ); } } /* ---------------------- CG_StasisTeleporter New stasis teleporter effect ---------------------- */ void CG_StasisTeleporter( vec3_t origin, float radius, qboolean exit ) { vec3_t up = { 0,0,1 }, sRGB, eRGB, org; float alpha = sin( cg.time * 0.005 ) * 0.2 + 0.3; FXCylinder *fx; if ( exit ) { // these are the exit colors VectorSet( sRGB, 0.0f, 0.8f, 1.0f ); // light-blue VectorSet( eRGB, 0.0f, 0.0f, 1.0f ); // blue } else { // these are the colors for the active type teleporters VectorSet( sRGB, 0.5f, 0.2f, 1.0f ); // bluish-purple VectorSet( eRGB, 1.0f, 0.0f, 0.4f ); // redish-purple } // Designers wanted the effects shifted down a bit, but still have the fx entity such that it gets placed so that it sits perfectly // on top of the jump pads for ease of placement. VectorCopy( origin, org ); org[2] -= 16; fx = FX_AddCylinder( org, up, 42.0f, 256.0f, 36.0f, 16.0f, 52.0f, 32.0f, alpha, 0.0f, sRGB, eRGB, 100, cgs.media.psychicShader, 0.25f ); if ( fx == NULL ) return; fx->SetSTScale( 1.0f ); fx->SetFlags( FXF_WRAP ); if ( exit ) { // We are an exit type teleporter, so skip all of the proximity stuff return; } if ( radius < 256 ) { vec3_t end, mid, vel, accel; float scale = ( 256 - radius ) * ( random() * 0.5f + 0.5f ); VectorMA( origin, 80, up, end ); FX_AddLine( org, end, 1, 1, scale * 4, 1.0, 0.0, 120, cgs.media.blueParticleShader ); // take the average of the two points to get the midpoint VectorAdd( org, end, mid ); VectorScale( mid, 0.5f, mid ); for ( int i = 0; i < 2; i++ ) { VectorSet( vel, crandom(), crandom(), crandom() * 2 ); VectorScale( vel, random() * 24 + scale * 0.25f, vel ); VectorScale( vel, -0.2f, accel ); if ( rand() & 1 ) { FX_AddSprite( mid, vel, accel, scale * 0.1f, scale * -0.2f, 0.3f, 0.0f, 0.0, 0.0, 500, cgs.media.ltblueParticleShader ); } else { FX_AddSprite( mid, vel, accel, scale * 0.1f, scale * -0.2f, 0.3f, 0.0f, 0.0, 0.0, 500, cgs.media.purpleParticleShader ); } } } } /* ---------------------- CG_StasisMushrooms Ambient effect for when the stasis mushrooms get used. ---------------------- */ bool MushroomThink( FXPrimitive *fx, centity_t *ent ) { fx->UpdateOrigin(); fx->UpdateAlpha(); fx->UpdateRGB(); fx->UpdateScale(); for ( int i = 0; i < 3; i++ ) { fx->m_velocity[i] += crandom() * 300.0f * cg.frametime * 0.001f; } return true; } void CG_StasisMushrooms( vec3_t origin, int count ) { vec3_t up, org; float scale, alpha; for ( int i = 0; i < count; i++ ) { // Make the thing move mostly up VectorSet( up, crandom() * 12.0f, crandom() * 12.0f, random() * 12.0f + 8.0f ); VectorSet( org, origin[0] + crandom() * 4.0f, origin[1] + crandom() * 4.0f, origin[2] - 4.0f ); scale = random() + 1.0f; alpha = random() * 0.4f + 0.3f; // Use a couple of different kinds to break up the monotony if ( rand() & 1 ) { FX_AddParticle( NULL, org, up, NULL, scale, -1.0f, alpha, 0.1f, 0.0f, 0.0f, 600 + random() * 600, cgs.media.ltblueParticleShader, 0, MushroomThink ); } else { FX_AddParticle( NULL, org, up, NULL, scale, -1.0f, alpha, 0.1f, 0.0f, 0.0f, 600 + random() * 600, cgs.media.blueParticleShader, 0, MushroomThink ); } } } /* ---------------------- CG_DreadnoughtBeamGlow Focusing beam effect ---------------------- */ void CG_DreadnoughtBeamGlow( vec3_t origin, vec3_t normal, float radius ) { FX_AddQuad( origin, normal, NULL, NULL, radius + random() * 10, crandom() * 20, 1.0f, 0.1f, random() * 360, crandom() * 20, 0, 300, cgs.media.bigBoomShader ); } /* ---------------------- CG_MagicSmoke Smoke effect for reaver popping out of vats ---------------------- */ void CG_MagicSmoke( vec3_t origin ) { int i; float v; vec3_t org, vel, accel; for ( i = 0; i < 48; i++ ) { org[0] = origin[0] + crandom() * 16; org[1] = origin[1] + crandom() * 16; org[2] = origin[2] + crandom() * 40; VectorSet( vel, crandom(), crandom(), crandom() ); v = random() * 32 + 32; VectorScale( vel, v, vel ); VectorScale( vel, -3, accel ); FX_AddSprite( org, vel, accel, random() * 32 + 32, -(random() * 32 + 8), 1.0f, 0.0f, crandom() * 360, 0, random() * 600 + 400, cgs.media.steamShader ); } } void CG_FountainSpurt( vec3_t org ) { int i, t; vec3_t org1, org2, cpt1, cpt2; vec3_t dir, dir2; vec3_t rgb = { 0.4f, 0.7f, 0.8f }; FXBezier *fxb; // offset table, could have used sin/cos, I suppose const float m[][2] = { 1, 0, 0, 1, -1, 0, 0, -1 }; // The origin shouldn't be in solid, otherwise the ent won't think. So, place the spawner above // the solid object, then move the spout spawn points down to where they should be. org[2] -= 56; // magic number stuff // Create four spouts for ( i = 0; i < 4; i++ ) { // Move the spout out from the exact center VectorCopy( org, org1 ); org1[0] += 35 * m[i][0]; org1[1] += 35 * m[i][1]; // Create our Bezier path control points VectorSet( cpt1, 50 * m[i][0], 50 * m[i][1], 0 ); VectorAdd( org1, cpt1, cpt1 ); VectorSet( cpt2, 60 * m[i][0], 60 * m[i][1], -78 ); VectorAdd( org1, cpt2, cpt2 ); // Create the second endpoint--for now just try and use the last control point VectorCopy( cpt2, org2 ); // Add the main spout fxb = FX_AddBezier( org1, org2, cpt1, cpt2, NULL, NULL, NULL, NULL, 4, 15, 0.5f, 0.3f, 90, cgs.media.fountainShader, FXF_WRAP ); if ( fxb ) fxb->SetSTScale( 0.7f ); // Add a hazy faint spout fxb = FX_AddBezier( org1, org2, cpt1, cpt2, NULL, NULL, NULL, NULL, 10, 15, 0.1f, 0.0f, 200, cgs.media.fountainShader, FXF_WRAP ); if ( fxb ) fxb->SetSTScale( 0.7f ); // Create misty bits at the impact point VectorSet( dir, crandom(), crandom(), crandom() + 4 ); // always move mostly up VectorScale( dir, random() * 3 + 2, dir ); FX_AddSprite( org2, dir, NULL, 20, -8, 0.3f, 0.0, 0, 0, 600, cgs.media.steamShader ); // ripple shader VectorSet( dir, 0, 0, 1 ); // normal VectorSet( dir2, crandom() * 8, crandom() * 8, 0 ); // random drift FX_AddQuad( org2, dir, dir2, dir2, 14.0f, 6.0f + random() * 16.0f, 0.2f, 0.0f, crandom() * 50, 0, 0, 800, cgs.media.rippleShader ); // Spray from nozzle for ( t = 0; t < 2; t++ ) { VectorSet( dir, 45 * m[i][0] + crandom() * 12, 45 * m[i][1] + crandom() * 12, crandom() * 16 ); VectorSet( dir2, -5 * m[i][0], -5 * m[i][1], -95 ); FX_AddSprite( org1, dir, dir2, 0.9f, 0.0f, 0.7f, 0.1f, rgb, rgb, 0.0f, 0.0f, 400.0f, cgs.media.waterDropShader ); } // Impact splashes for ( t = 0; t < 3; t++ ) { VectorCopy( org2, org1 ); org1[0] += crandom() * 2; org1[1] += crandom() * 2; VectorSet( dir, m[i][0] * 14 + crandom() * 16, m[i][1] * 14 + crandom() * 16, 50 + random() * 50 ); VectorSet( dir2, 0, 0, -250 ); FX_AddSprite( org1, dir, dir2, 1.1f, -0.4f, 0.7f, 0.1f, rgb, rgb, 0.0f, 0.0f, 400.0f, cgs.media.waterDropShader ); } } }