//Stasis weapon effects #include "cg_local.h" #include "fx_public.h" void FX_StasisDischarge( vec3_t origin, vec3_t normal, int count, float dist_out, float dist_side ); #define FX_STASIS_ALT_RIGHT_OFS 0.10 #define FX_STASIS_ALT_UP_OFS 0.02 #define FX_MAXRANGE_STASIS 8192 /* ------------------------- FX_StasisShot Alt-fire, beam that shrinks to its impact point ------------------------- */ void FX_SmallStasisBeam(centity_t *cent, vec3_t start, vec3_t dir) { vec3_t end, org, vel = { 0,0,-4}; trace_t tr; float r; int i, ct, t; VectorMA(start, FX_MAXRANGE_STASIS, dir, end); CG_Trace(&tr, start, NULL, NULL, end, cent->currentState.number, MASK_SHOT); // Beam FX_AddLine( start, tr.endpos, 1.0f, 3.0f, 4.0f, 0.8f, 0.0f, 400.0f, cgs.media.stasisAltShader ); // Do a quick LOD for number of decay particles ct = tr.fraction * ( FX_MAXRANGE_STASIS * 0.02 ); if ( ct < 12 ) ct = 12; else if ( ct > 24 ) ct = 24; for ( i = 0; i < ct; i++ ) { r = random() * tr.fraction * ( FX_MAXRANGE_STASIS * 0.5 ); VectorMA( start, r, dir, org ); for ( t = 0; t < 3; t++ ) org[t] += crandom(); if ( rand() & 1 ) FX_AddSprite( org, vel, NULL, random() + 1.5, -3, 1.0, 1.0, 0.0, 0.0, 500, cgs.media.blueParticleShader ); else FX_AddSprite( org, vel, NULL, random() + 1.5, -3, 1.0, 1.0, 0.0, 0.0, 500, cgs.media.purpleParticleShader ); } // Impact graphic if needed. if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { // Hit an entity. // Expanding rings FX_AddSprite( tr.endpos, NULL, NULL, 1, 60, 0.8f, 0.2f, random() * 360, 0, 400, cgs.media.stasisRingShader ); // Impact effect FX_AddSprite( tr.endpos, NULL, NULL, 5, 18, 1.0, 0.0, random() * 360, 0, 420, cgs.media.ltblueParticleShader ); } else if ( !(tr.surfaceFlags & SURF_NOIMPACT) ) { // Move me away from the wall a bit so that I don't z-buffer into it VectorMA( tr.endpos, 1.5, tr.plane.normal, end); // Expanding rings FX_AddQuad( end, tr.plane.normal, NULL, NULL, 1, 18, 0.8f, 0.2f, random() * 360, 0, 0, 400, cgs.media.stasisRingShader ); FX_AddQuad( end, tr.plane.normal, NULL, NULL, 1, 45, 0.8f, 0.2f, random() * 360, 0, 0, 300, cgs.media.stasisRingShader ); // Impact effect FX_AddQuad( end, tr.plane.normal, NULL, NULL, 5, 25, 1.0, 0.0, random() * 360, 0, 0, 500, cgs.media.blueParticleShader ); FX_AddQuad( end, tr.plane.normal, NULL, NULL, 4, 18, 1.0, 0.0, random() * 360, 0, 0, 420, cgs.media.ltblueParticleShader ); CG_ImpactMark( cgs.media.scavMarkShader, end, tr.plane.normal, random()*360, 1,1,1,0.6f, qfalse, 6 + random() * 2, qfalse ); } FX_AddSprite( tr.endpos, NULL, NULL, Q_flrand(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader ); // Pass the end position back to the calling function (yes, I know). VectorCopy(tr.endpos, dir); } void FX_StasisShot( centity_t *cent, vec3_t end, vec3_t start ) { vec3_t fwd, newdir, org, vel = { 0,0,-4}; int i, t, ct; float len, r; vec3_t up={0, 0, 1}, right; int bolt1, bolt2; vec3_t bolt1vec, bolt2vec; // Choose which bolt will have the electricity accent. bolt1 = Q_irand(0,2); bolt2 = Q_irand(0,4); VectorSubtract( end, start, fwd ); len = VectorNormalize( fwd ); // Beam FX_AddLine( end, start, 1.0f, 4.0f, 6.0f, 0.8f, 0.0f, 500.0f, cgs.media.stasisAltShader ); // Do a quick LOD for number of decay particles ct = len * 0.03; if ( ct < 16 ) ct = 16; else if ( ct > 32 ) ct = 32; for ( i = 0; i < ct; i++ ) { r = random() * len * 0.5; VectorMA( start, r, fwd, org ); for ( t = 0; t < 3; t++ ) org[t] += crandom(); if ( rand() & 1 ) FX_AddSprite( org, vel, NULL, random() + 2, -4, 1.0, 1.0, 0.0, 0.0, 600, cgs.media.blueParticleShader); else FX_AddSprite( org, vel, NULL, random() + 2, -4, 1.0, 1.0, 0.0, 0.0, 600, cgs.media.purpleParticleShader); } if (bolt1==0) { VectorCopy(end, bolt1vec); } else if (bolt2==0) { VectorCopy(end, bolt2vec); } CrossProduct(fwd, up, right); CrossProduct(right, fwd, up); // Change the "fake up" (0,0,1) to a "real up" (perpendicular to the forward vector). // Fire a shot up and to the right. VectorMA(fwd, FX_STASIS_ALT_RIGHT_OFS, right, newdir); VectorMA(newdir, FX_STASIS_ALT_UP_OFS, up, newdir); FX_SmallStasisBeam(cent, start, newdir); if (bolt1==1) { VectorCopy(newdir, bolt1vec); } else if (bolt2==1) { VectorCopy(newdir, bolt2vec); } // Fire a shot up and to the left. VectorMA(fwd, -FX_STASIS_ALT_RIGHT_OFS, right, newdir); VectorMA(newdir, FX_STASIS_ALT_UP_OFS, up, newdir); FX_SmallStasisBeam(cent, start, newdir); if (bolt1==2) { VectorCopy(newdir, bolt1vec); } else if (bolt2==2) { VectorCopy(newdir, bolt2vec); } // Fire a shot a bit down and to the right. VectorMA(fwd, 2.0*FX_STASIS_ALT_RIGHT_OFS, right, newdir); VectorMA(newdir, -0.5*FX_STASIS_ALT_UP_OFS, up, newdir); FX_SmallStasisBeam(cent, start, newdir); if (bolt1==3) { VectorCopy(newdir, bolt1vec); } else if (bolt2==3) { VectorCopy(newdir, bolt2vec); } // Fire a shot up and to the left. VectorMA(fwd, -2.0*FX_STASIS_ALT_RIGHT_OFS, right, newdir); VectorMA(newdir, -0.5*FX_STASIS_ALT_UP_OFS, up, newdir); FX_SmallStasisBeam(cent, start, newdir); if (bolt1==4) { VectorCopy(newdir, bolt1vec); } else if (bolt2==4) { VectorCopy(newdir, bolt2vec); } // Put a big gigant-mo sprite at the muzzle end so people can't see the crappy edges of the line FX_AddSprite( start, NULL, NULL, random()*3 + 15, -20, 1.0, 0.5, 0.0, 0.0, 600, cgs.media.blueParticleShader); // Do an electrical arc to one of the impact points. // FX_AddElectricity( start, bolt1vec, 0.2f, 15.0, -15.0, 1.0, 0.5, 100, cgs.media.dnBoltShader, 0.1 ); // if (bolt1 != bolt2) // { // ALSO do an electrical arc to another point. // FX_AddElectricity( bolt1vec, bolt2vec, 0.2f, 15.0, -15.0, 1.0, 0.5, Q_flrand(100,200), cgs.media.dnBoltShader, 0.5 ); // } } /* ------------------------- FX_StasisShotImpact Alt-fire, impact effect ------------------------- */ void FX_StasisShotImpact( vec3_t end, vec3_t dir ) { vec3_t org; // Move me away from the wall a bit so that I don't z-buffer into it VectorMA( end, 0.5, dir, org ); // Expanding rings FX_AddQuad( org, dir, NULL, NULL, 1, 80, 0.8f, 0.2f, random() * 360, 360, 0, 400, cgs.media.stasisRingShader ); // Impact effect FX_AddQuad( org, dir, NULL, NULL, 7, 35, 1.0, 0.0, random() * 360, 0, 0, 500, cgs.media.blueParticleShader ); FX_AddQuad( org, dir, NULL, NULL, 5, 25, 1.0, 0.0, random() * 360, 0, 0, 420, cgs.media.ltblueParticleShader ); } /* ------------------------- FX_StasisShotMiss Alt-fire, miss effect ------------------------- */ void FX_StasisShotMiss( vec3_t end, vec3_t dir ) { vec3_t org; // Move me away from the wall a bit so that I don't z-buffer into it VectorMA( end, 0.5, dir, org ); // Expanding rings FX_AddQuad( org, dir, NULL, NULL, 1, 24, 0.8f, 0.2f, random() * 360, 360, 0, 400, cgs.media.stasisRingShader ); FX_AddQuad( org, dir, NULL, NULL, 1, 60, 0.8f, 0.2f, random() * 360, -360, 0, 300, cgs.media.stasisRingShader ); // Impact effect FX_AddQuad( org, dir, NULL, NULL, 7, 35, 1.0, 0.0, random() * 360, 0, 0, 500, cgs.media.blueParticleShader ); FX_AddQuad( org, dir, NULL, NULL, 5, 25, 1.0, 0.0, random() * 360, 0, 0, 420, cgs.media.ltblueParticleShader ); CG_ImpactMark( cgs.media.scavMarkShader, org, dir, random()*360, 1,1,1,0.6f, qfalse, 8 + random() * 2, qfalse ); // FX_AddSprite( end, NULL, qfalse, flrandom(40,60), -50, 1.0, 0.0, random() * 360, 0, 500, cgs.media.blueParticleShader ); } /* ------------------------- FX_StasisProjectileThink Main fire, with crazy bits swirling around main projectile ------------------------- */ void FX_StasisProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) { int size = 0; if ( cent->gent == NULL ) return; size = cent->gent->count; if ( size < 3 ) FX_AddSprite( cent->lerpOrigin, NULL, NULL, size * 18.0f + ( random() * size * 4.0f ), 0.0f, 1.0f, 0.0f, 0, 0.0f, 1, cgs.media.blueParticleShader ); else { // Only do this extra crap if you're the big cheese vec3_t forward, right, up; float radius, temp; vec3_t org; // convert direction of travel into normalized forward vector if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0 ) { forward[2] = 1; } MakeNormalVectors( forward, right, up ); // Main projectile FX_AddSprite( cent->lerpOrigin, NULL, NULL, ( size * size ) * 4.5f + ( random() * size * 4.0f ), 0, 1.0, 1.0, 0.0, 0.0, 1, cgs.media.blueParticleShader ); // Crazy polar coordinate plotting stuff--for particle swarm // FIXME: SIN and COS hell! radius = 30 * sin( cg.time * 0.002 * 5 ); temp = radius * cos( cg.time * 0.002 ); VectorMA( cent->lerpOrigin, temp, right, org ); temp = radius * sin( cg.time * 0.002 ); VectorMA( org, temp, up, org ); // Main bit FX_AddSprite( org, NULL, NULL, random() * 8 + 4, 0, 1.0, 1.0, 0.0, 0.0, 1, cgs.media.purpleParticleShader ); // Glowy bit FX_AddSprite( org, NULL, NULL, random() * 8 + 24, 0, 0.3f, 0.3f, 0.0, 0.0, 1, cgs.media.purpleParticleShader ); // Tail bit FX_AddLine( org, cent->gent->pos1, 1.0, 2.0, -1.0, 0.5, 0.0, 300, cgs.media.altIMOD2Shader ); VectorCopy( org, cent->gent->pos1 ); radius = 30 * sin( cg.time * 0.002 * 3 ); temp = radius * cos( cg.time * 0.002 + 3.141 ); VectorMA( cent->lerpOrigin, temp, right, org ); temp = radius * sin( cg.time * 0.002 + 3.141 ); VectorMA( org, temp, up, org ); // Main bit FX_AddSprite( org, NULL, NULL, random() * 8 + 4, 0, 1.0, 1.0, 0.0, 0.0, 1, cgs.media.purpleParticleShader ); // Glowy bit FX_AddSprite( org, NULL, NULL, random() * 8 + 24, 0, 0.3f, 0.3f, 0.0, 0.0, 1, cgs.media.purpleParticleShader ); // Tail bit FX_AddLine( org, cent->gent->pos2, 1.0, 2.0, -1.0, 0.5, 0.0, 300, cgs.media.altIMOD2Shader ); VectorCopy( org, cent->gent->pos2 ); radius = 30 * sin( cg.time * 0.002 * 3.5 ); temp = radius * cos( cg.time * 0.002 + 3.141 * 0.5); VectorMA( cent->lerpOrigin, temp, right, org ); temp = radius * sin( cg.time * 0.002 + 3.141 * 0.5 ); VectorMA( org, temp, up, org ); // Main bit FX_AddSprite( org, NULL, NULL, random() * 8 + 4, 0, 1.0, 1.0, 0.0, 0.0, 1, cgs.media.purpleParticleShader ); // Glowy bit FX_AddSprite( org, NULL, NULL, random() * 8 + 24, 0, 0.3f, 0.3f, 0.0, 0.0, 1, cgs.media.purpleParticleShader ); radius = 30 * sin( cg.time * 0.002 * 4.5 ); temp = radius * cos( cg.time * 0.002 + 3.141 * 1.5); VectorMA( cent->lerpOrigin, temp, right, org ); temp = radius * sin( cg.time * 0.002 + 3.141 * 1.5 ); VectorMA( org, temp, up, org ); // Main bit FX_AddSprite( org, NULL, NULL, random() * 8 + 4, 0, 1.0, 1.0, 0.0, 0.0, 1, cgs.media.purpleParticleShader ); // Glowy bit FX_AddSprite( org, NULL, NULL, random() * 8 + 24, 0, 0.3f, 0.3f, 0.0, 0.0, 1, cgs.media.purpleParticleShader ); } return; } /* ------------------------- FX_StasisWeaponHitWall Main fire impact ------------------------- */ #define NUM_DISCHARGES 2 #define DISCHARGE_DIST 8 #define DISCHARGE_SIDE_DIST 24 void FX_StasisWeaponHitWall( vec3_t origin, vec3_t dir, int size ) { // Generate "crawling" electricity FX_StasisDischarge( origin, dir, NUM_DISCHARGES, DISCHARGE_DIST, DISCHARGE_SIDE_DIST ); // Set an oriented residual glow effect FX_AddQuad( origin, dir, NULL, NULL, size * size * 12.0f, -60.0f, 1.0f, 1.0f, 0, 0, 0, 300, cgs.media.blueParticleShader ); CG_ImpactMark( cgs.media.scavMarkShader, origin, dir, random()*360, 1,1,1,0.6f, qfalse, size * size * 4 + 1, qfalse ); // Only play the impact sound and throw off the purple particles when it's the main projectile if ( size < 3 ) return; vec3_t vel, accel; int i, t; for ( i = 0; i < 4; i++ ) { for ( t = 0; t < 3; t++ ) vel[t] = ( dir[t] + crandom() * 0.9 ) * ( random() * 100 + 250 ); VectorScale( vel, -2.2, accel ); FX_AddSprite( origin, vel, accel, random() * 8 + 8, 0, 1.0, 0.0, 0.0, 0.0, 200, cgs.media.purpleParticleShader ); } cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cg_weapons[WP_STASIS].missileHitSound ); } /* ------------------------- FX_StasisWeaponHitPlayer Main fire impact ------------------------- */ void FX_StasisWeaponHitPlayer( vec3_t origin, vec3_t dir, int size ) { int i; vec3_t right, up, pos, accel; // FIXME: This is mostly just a stand-in, though it may be ok MakeNormalVectors( dir, right, up ); // Send some particles scurrying along the impact plane for ( i = 0; i < 6; i++ ) { VectorClear( pos ); VectorMA( pos, crandom(), right, pos ); VectorMA( pos, crandom(), up, pos ); VectorNormalize( pos ); VectorScale( pos, random() * size * 48 + 32, pos ); VectorScale( pos, -2, accel ); FX_AddSprite( origin, pos, accel, size * size * random() * 0.5f, -( size * random() * 2 ), 1.0f, 0.0f, 0, 0, 300, cgs.media.blueParticleShader ); } } /* ------------------------- FX_StasisBoltThink Draw the bolts ------------------------- */ void FX_StasisBoltThink( vec3_t start, vec3_t end, vec3_t dir, vec3_t user ) { vec3_t mid; VectorSubtract( end, start, mid ); VectorScale( mid, 0.1f + (random() * 0.8), mid ); VectorAdd( start, mid, mid ); VectorMA(mid, 3.0f + (random() * 10.0f), dir, mid ); FX_AddElectricity( mid, start, 0.5, 0.75 + random() * 0.75, 0.0, 1.0, 0.5, 75, cgs.media.bolt2Shader, 9 ); FX_AddElectricity( mid, end, 0.5, 0.75 + random() * 0.75, 1.0, 1.0, 0.5, 75, cgs.media.bolt2Shader, FXF_TAPER ); } /* ------------------------- FX_OrientedBolt Creates new bolts for a while ------------------------- */ void FX_OrientedBolt( vec3_t start, vec3_t end, vec3_t dir ) { FX_AddSpawner( start, end, dir, NULL, 50, random() * 75, 300.0f + random() * 300, (void *) FX_StasisBoltThink, NULL, 680); } /* ------------------------- FX_StasisDischarge Fun "crawling" electricity ( credit goes to Josh for this one ) ------------------------- */ void FX_StasisDischarge( vec3_t origin, vec3_t normal, int count, float dist_out, float dist_side ) { trace_t trace; vec3_t org, dir, dest; vec3_t vr; int i; int discharge = dist_side; vectoangles( normal, dir ); dir[ROLL] += random() * 360; for (i = 0; i < count; i++) { //Move out a set distance VectorMA( origin, dist_out, normal, org ); //Even out the hits dir[ROLL] += (360 / count) + (rand() & 31); AngleVectors( dir, NULL, vr, NULL ); //Move to the side in a random direction discharge += (int)( crandom() * 8.0f ); VectorMA( org, discharge, vr, org ); //Trace back to find a surface VectorMA( org, -dist_out * 3, normal, dest ); cgi_CM_BoxTrace( &trace, org, dest, NULL, NULL, 0, MASK_SHOT ); //No surface found, start over if (trace.fraction == 1) continue; //Connect the two points with bolts FX_OrientedBolt( origin, trace.endpos, normal ); } } /* ------------------------- FX_StasisAmbientThings Ambient sparks of light that swarm in a given area ------------------------- */ // This is a "random" table that has been tweaked to provide a nice distribution of ambient "thingies", the last field is a perc of max radius // There are 10 here now....this had better correspond with the count the designers use or the results could be unpredictable static int ang_offset[][3] = { -138, -208, 100, -292, -284, 30, 197, 67, 70, 257, -115, 40, 177, 286, 80, 19, 239, 20, -337, -347, 60, -9, -195, 10, -186, -52, 50, -86, -51, 90}; void FX_StasisAmbientThings( centity_t *cent ) { int i, ct = 0; float radius = 0, variance = 0; vec3_t origin, angvect, temp; if ( cent->gent == NULL ) return; ct = cent->gent->count; variance = cent->gent->random * 0.01; // Update the angle of the particle (position on the ellipsoid). VectorMA( cent->gent->pos1, cg.frametime * 0.001, cent->gent->pos2, cent->gent->pos1 ); for ( i = 0; i < ct; i++ ) { // Calc allowable variance in radius. radius = cent->gent->radius * ( ( 1 - variance) + (ang_offset[i][2] * 0.01 * variance) ); // Refresh this every time. VectorCopy( cent->gent->pos1, temp ); // This uses the angular offset table to create the swarm temp[0] += ang_offset[i][0]; temp[1] += ang_offset[i][1]; // Update the actual position of the particle. AngleVectors( temp, angvect, NULL, NULL ); origin[0] = (8 * cos(cg.time * 0.004 + i ) + radius) * angvect[0] + cent->lerpOrigin[0]; origin[1] = (8 * sin(cg.time * 0.005 + i ) + radius) * angvect[1] + cent->lerpOrigin[1]; origin[2] = (8 * sin(cg.time * 0.006 + i ) + radius) * angvect[2] + cent->lerpOrigin[2]; // Main core followed by much larger, fainter aura....sort of looks irridescent or something FX_AddSprite( origin, NULL, NULL, random() * 2.0f + 2.5f, 2.0f, 1.0f, 0.0f, 0.0f, 0.0f, 100.0f, cgs.media.ltblueParticleShader ); FX_AddSprite( origin, NULL, NULL, random() * 6.0f + 6.0f, 2.0f, 0.3f, 0.0f, 0.0f, 0.0f, 100.0f, cgs.media.purpleParticleShader ); if ( cent->gent->s.eFlags & EF_EYEBEAM && cent->gent->enemy && !Q_irand(0, 5) ) {//Beam effect to entity it's fixing FX_AddElectricity( origin, cent->gent->enemy->currentOrigin, 1.0f, 0.75f + ( random() * 0.75f ), 0.0f, Q_flrand(0.7f, 1.0f), 0.0f, 50.0f, cgs.media.stasisBoltShader, FXF_TAPER ); } } } /* ------------------------- FX_StasisAttackThink Stasis alien attack projectile ------------------------- */ void FX_StasisAttackThink( centity_t *cent, const struct weaponInfo_s *weapon ) { if ( cent->gent == NULL ) return; FX_AddSprite( cent->lerpOrigin, NULL, NULL, 16.0f + random() * 27.0f, 0.0f, 0.2f, 0.2f, 0, 0.0f, 1, cgs.media.purpleParticleShader ); FX_AddSprite( cent->lerpOrigin, NULL, NULL, 6.0f + random() * 8.0f, 0.0f, 1.0f, 0.0f, 0, 0.0f, 1, cgs.media.ltblueParticleShader ); vec3_t end, dir, angles, right; float len; int ct, i; // Convert direction of travel into dir vector VectorCopy( cent->gent->s.pos.trDelta, dir ); VectorNormalize( dir ); vectoangles( dir, angles ); ct = rand() & 1; // return 1 or zero for ( i = 0; i <= ct; i++ ) { angles[2] = random() * 360; AngleVectors( angles, NULL, right, NULL ); len = crandom() * 6; len += (len > 0 ? 4 : -4); VectorMA( cent->lerpOrigin, len, right, end ); FX_AddElectricity( cent->lerpOrigin, end, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, cgs.media.boltShader, FXF_TAPER, 1.4f ); } } /* ------------------------- FX_StasisAttackHitPlayer Stasis alien attack projectile ------------------------- */ void FX_StasisAttackHitPlayer( vec3_t origin, vec3_t dir ) { // Just call this for now FX_StasisWeaponHitPlayer( origin, dir, 2 ); cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cg_weapons[WP_STASIS_ATTACK].missileHitSound ); } /* ------------------------- FX_StasisAttackHitWall Stasis alien attack projectile ------------------------- */ void FX_StasisAttackHitWall( vec3_t origin, vec3_t dir ) { // Just call this for now FX_StasisWeaponHitWall( origin, dir, 2 ); // A sound will get played in stasis weapon hit wall, but put this in here for later //cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgi_S_RegisterSound("sound/weapons/stasis_alien/hit_wall.wav") ); cgi_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cg_weapons[WP_STASIS_ATTACK].missileHitSound ); } /* ------------------------- FX_StasisCharge Stasis charge build-up that happens before a fire ------------------------- */ void FX_StasisCharge( vec3_t origin, vec3_t dir, float perc_done ) { FX_AddSprite( origin, NULL, NULL, 32.0f + random() * 54.0f, 0.0f, 0.2f * perc_done, 0.2f, 0, 0.0f, 175, cgs.media.purpleParticleShader ); FX_AddSprite( origin, NULL, NULL, 12.0f + random() * 16.0f, 0.0f, 1.0f * perc_done, 0.2f, 0, 0.0f, 175, cgs.media.blueParticleShader ); vec3_t end, angles, forward; float len; int ct, i; // Convert direction of travel into dir vector vectoangles( dir, angles ); ct = rand() & 1; // return 1 or zero for ( i = 0; i <= ct; i++ ) { angles[2] = random() * 360; AngleVectors( angles, NULL, forward, NULL ); len = crandom() * 12; len += (len > 0 ? 8 : -8); VectorMA( origin, len, forward, end ); FX_AddElectricity( origin, end, 1.0, 1.5f, 0.0, 1.0 * perc_done, 0.2f, 150.0, cgs.media.boltShader, FXF_TAPER, 1.6f ); } } /* ------------------------- FX_StasisBeamIn Stasis effect for when they are beaming in Particles that race out from the core, then slow down to a stop at the end of their life ------------------------- */ void FX_StasisBeamInParticles( vec3_t origin, vec3_t angles, vec3_t velocity, vec3_t user ) { vec3_t dir, vel, accel; float len, acceleration, vf; int time; for ( int i = 0; i < 4; i++ ) { // Pick a random direction.. VectorSet( dir, crandom(), crandom(), crandom() ); VectorNormalize( dir ); // ..pick a random length len = random() * 16 + 48; // Now build an acceleration vector acceleration = 256 + random() * 64; VectorScale( dir, -acceleration, accel ); // FIXME: This mostly works like I'd like, though I wonder if this can/should be cleaned up // Calculate how long the thing will take to travel that distance--convert to the timescale we use time = sqrt( 2 / acceleration * len ) * 1000.0f; vf = sqrt( 2 * len * acceleration ); // calculate 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( origin, vel, accel, 1 + random(), 4 + random() * 4, 1.0, 0.0, 0,0, time, cgs.media.purpleParticleShader ); } else { FX_AddSprite( origin, vel, accel, 1 + random(), 4 + random() * 4, 1.0, 0.0, 0,0, time, cgs.media.ltblueParticleShader ); } } } void FX_StasisBeamIn( vec3_t origin ) { vec3_t up = {0,0,1}, org, start, end; VectorCopy( origin, org ); org[2] += 16; FX_AddSpawner( org, NULL, NULL, NULL, 10, 5, 120, FX_StasisBeamInParticles, NULL ); VectorMA( org, 60, up, start ); VectorMA( org, -50, up, end ); FX_AddLine( start, end, 1, 1, 200, 1.0, 0.0, 600, cgs.media.blueParticleShader ); FX_AddLine( start, end, 1, 1, 100, 1.0, 0.0, 600, cgs.media.blueParticleShader ); } /* ------------------------- FX_StasisBeamOut Stasis effect for when they are "dead" and need to beam-out Particles that accelerate into the core of the effect ------------------------- */ //void FX_StasisBeamOutParticles( vec3_t origin, vec3_t angles, vec3_t velocity, vec3_t user ) void FX_StasisBeamOutParticles( vec3_t origin ) { vec3_t dir, vel, accel; float acceleration; int time; for ( int i = 0; i < 40; i++ ) { // Pick a random direction.. VectorSet( dir, crandom(), crandom(), crandom() ); VectorNormalize( dir ); // Now build an acceleration vector acceleration = 220; VectorScale( dir, acceleration, vel ); VectorScale( vel, -0.4, accel ); time = 800.0f; if ( rand() & 1 ) { FX_AddSprite( origin, vel, accel, 6 + random() * 6, -(4 + random() * 6), 1.0, 0.0, 0,0, time, cgs.media.purpleParticleShader ); } else if ( rand() & 1 ) { FX_AddSprite( origin, vel, accel, 6 + random() * 6, -(4 + random() * 6), 1.0, 0.0, 0,0, time, cgs.media.ltblueParticleShader ); } else { FX_AddSprite( origin, vel, accel, 6 + random() * 6, -(4 + random() * 6), 1.0, 0.0, 0,0, time, cgs.media.blueParticleShader ); } } } void FX_StasisBeamOut( vec3_t origin ) { vec3_t up = {0,0,1}, org, start, end; VectorCopy( origin, org ); org[2] += 16; //FX_AddSpawner( org, NULL, NULL, NULL, 20, 0, 150, FX_StasisBeamOutParticles, NULL ); FX_StasisBeamOutParticles( org ); VectorMA( org, 80, up, start ); VectorMA( org, -50, up, end ); FX_AddLine( start, end, 1, 40, 128, 1.0, 0.0, 400, cgs.media.blueHitShader ); VectorMA( org, 20, up, start ); VectorMA( org, -15, up, end ); FX_AddLine( start, end, 1, 40, 500, 1.0, 0.0, 400, cgs.media.ghostRingShader ); } /* ------------------------- FX_StasisMineExplode ------------------------- */ void FX_StasisMineExplode( vec3_t origin ) { FXSprite *fx; FXTrail *particle; vec3_t rgb, org, moveDir, accel = { 0, 0, -400 }; int i; // make some elongated splashes for ( i = 0; i < 8; i++ ) { VectorSet( rgb, random() * 0.2f + 0.3f, random() * 0.6f + 0.1f, random() * 0.4f + 0.6f ); VectorSet( moveDir, crandom(), crandom(), random() ); VectorNormalize( moveDir ); particle = FX_AddTrail( origin, NULL, NULL, 8.0f, -1.0f, 4.0, -1.0, 1.0f, 0.5f, rgb, rgb, 0.4f, 1400.0f + random() * 500.0f, cgs.media.spooShader, rand() & FXF_BOUNCE ); if ( particle != NULL ) { FXE_Spray( moveDir, 80, 160, 0.1f, 256, (FXPrimitive *) particle ); } } // Make a real splash for ( i = 0; i < 12; i++ ) { VectorSet( org, origin[0] + crandom() * 4.0f, origin[1] + crandom() * 4.0f, origin[2] + crandom() * 4.0f ); for ( int j = 0; j < 3; j++ ) { moveDir[j] = crandom() * 55.0; } moveDir[2] += 120.0f; VectorSet( rgb, random() * 0.3f + 0.5f, random() * 0.2f + 0.3f, random() * 0.3f + 0.6f ); fx = FX_AddSprite( org, moveDir, accel, 4.0f + random() * 6.0f, -4.0f, 1.0f, 0.5f, rgb, rgb, 0, 0.0f, 250 + random() * 400, cgs.media.spooShader ); if ( fx == NULL ) return; fx->SetRoll( 0 ); } CG_Chunks( 0, org, moveDir, random() * 2 + 2, Q_irand(4, 6), MAT_STASIS, 0, 0.3f ); // Orient the explosion so that it faces the viewer VectorScale( cg.refdef.viewaxis[0], -1, moveDir ); CG_MakeExplosion( org, moveDir, cgs.media.explosionModel, 6, cgs.media.electricalExplosionSlowShader, 500, qfalse, random() * 0.2f + 0.7f ); }