#include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "../Weapon.h" #include "../client/ClientEffect.h" #include "../Projectile.h" #include "../ai/AI_Manager.h" const int LIGHTNINGGUN_NUM_TUBES = 3; const int LIGHTNINGGUN_MAX_PATHS = 3; const idEventDef EV_Lightninggun_RestoreHum( "", "" ); class rvLightningPath { public: idEntityPtr target; idVec3 origin; idVec3 normal; rvClientEffectPtr trailEffect; rvClientEffectPtr impactEffect; void StopEffects ( void ); void UpdateEffects ( const idVec3& from, const idDict& dict ); void Save ( idSaveGame* savefile ) const; void Restore ( idRestoreGame* savefile ); }; class rvWeaponLightningGun : public rvWeapon { public: CLASS_PROTOTYPE( rvWeaponLightningGun ); rvWeaponLightningGun( void ); ~rvWeaponLightningGun( void ); virtual void Spawn ( void ); virtual void Think ( void ); virtual void ClientStale ( void ); void PreSave ( void ); void PostSave ( void ); void Save ( idSaveGame* savefile ) const; void Restore ( idRestoreGame* savefile ); bool NoFireWhileSwitching( void ) const { return true; } protected: void UpdateTubes ( void ); // Tube effects rvClientEntityPtr tubeEffects[LIGHTNINGGUN_NUM_TUBES]; idInterpolate tubeOffsets[LIGHTNINGGUN_NUM_TUBES]; jointHandle_t tubeJoints[LIGHTNINGGUN_NUM_TUBES]; float tubeMaxOffset; float tubeThreshold; int tubeTime; rvClientEntityPtr trailEffectView; int nextCrawlTime; float range; jointHandle_t spireJointView; jointHandle_t chestJointView; rvLightningPath currentPath; // Chain lightning mod idList chainLightning; idVec3 chainLightningRange; private: void Attack ( idEntity* ent, const idVec3& dir, float power = 1.0f ); void UpdateChainLightning ( void ); void StopChainLightning ( void ); void UpdateEffects ( const idVec3& origin ); void UpdateTrailEffect ( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view = false ); stateResult_t State_Raise ( const stateParms_t& parms ); stateResult_t State_Lower ( const stateParms_t& parms ); stateResult_t State_Idle ( const stateParms_t& parms ); stateResult_t State_Fire ( const stateParms_t& parms ); void Event_RestoreHum ( void ); CLASS_STATES_PROTOTYPE ( rvWeaponLightningGun ); }; CLASS_DECLARATION( rvWeapon, rvWeaponLightningGun ) EVENT( EV_Lightninggun_RestoreHum, rvWeaponLightningGun::Event_RestoreHum ) END_CLASS /* ================ rvWeaponLightningGun::rvWeaponLightningGun ================ */ rvWeaponLightningGun::rvWeaponLightningGun( void ) { } /* ================ rvWeaponLightningGun::~rvWeaponLightningGun ================ */ rvWeaponLightningGun::~rvWeaponLightningGun( void ) { int i; if ( trailEffectView ) { trailEffectView->Stop( ); } currentPath.StopEffects( ); StopChainLightning( ); for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { if ( tubeEffects[i] ) { tubeEffects[i]->Stop( ); } } } /* ================ rvWeaponLightningGun::Spawn ================ */ void rvWeaponLightningGun::Spawn( void ) { int i; trailEffectView = NULL; nextCrawlTime = 0; chainLightning.Clear( ); // get hitscan range for our firing range = weaponDef->dict.GetFloat( "range", "10000" ); // Initialize tubes for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { tubeJoints[i] = viewModel->GetAnimator()->GetJointHandle ( spawnArgs.GetString ( va("joint_tube_%d",i), "" ) ); tubeOffsets[i].Init ( gameLocal.time, 0, 0, 0 ); } // Cache the max ammo for the weapon and the max tube offset tubeMaxOffset = spawnArgs.GetFloat ( "tubeoffset" ); tubeThreshold = owner->inventory.MaxAmmoForAmmoClass ( owner, GetAmmoNameForIndex ( ammoType ) ) / (float)LIGHTNINGGUN_NUM_TUBES; tubeTime = SEC2MS ( spawnArgs.GetFloat ( "tubeTime", ".25" ) ); spireJointView = viewModel->GetAnimator ( )->GetJointHandle ( "spire_1" ); if( gameLocal.GetLocalPlayer()) { chestJointView = gameLocal.GetLocalPlayer()->GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_hideGun_flash" ) ); } else { chestJointView = spireJointView; } chainLightningRange = spawnArgs.GetVec2( "chainLightningRange", "150 300" ); SetState ( "Raise", 0 ); } /* ================ rvWeaponLightningGun::Save ================ */ void rvWeaponLightningGun::Save ( idSaveGame* savefile ) const { int i; // Lightning Tubes for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { tubeEffects[i].Save ( savefile ); savefile->WriteInterpolate ( tubeOffsets[i] ); savefile->WriteJoint ( tubeJoints[i] ); } savefile->WriteFloat ( tubeMaxOffset ); savefile->WriteFloat ( tubeThreshold ); savefile->WriteInt ( tubeTime ); // General trailEffectView.Save ( savefile ); savefile->WriteInt ( nextCrawlTime ); savefile->WriteFloat ( range ); savefile->WriteJoint ( spireJointView ); savefile->WriteJoint ( chestJointView ); currentPath.Save ( savefile ); // Chain Lightning mod savefile->WriteInt ( chainLightning.Num() ); for ( i = 0; i < chainLightning.Num(); i ++ ) { chainLightning[i].Save ( savefile ); } savefile->WriteVec3 ( chainLightningRange ); } /* ================ rvWeaponLightningGun::Restore ================ */ void rvWeaponLightningGun::Restore ( idRestoreGame* savefile ) { int i; int num; float f; // Lightning Tubes for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { tubeEffects[i].Restore ( savefile ); savefile->ReadFloat ( f ); tubeOffsets[i].SetStartTime ( f ); savefile->ReadFloat ( f ); tubeOffsets[i].SetDuration ( f ); savefile->ReadFloat ( f ); tubeOffsets[i].SetStartValue ( f ); savefile->ReadFloat ( f ); tubeOffsets[i].SetEndValue ( f ); savefile->ReadJoint ( tubeJoints[i] ); } savefile->ReadFloat ( tubeMaxOffset ); savefile->ReadFloat ( tubeThreshold ); savefile->ReadInt ( tubeTime ); // General trailEffectView.Restore ( savefile ); savefile->ReadInt ( nextCrawlTime ); savefile->ReadFloat ( range ); savefile->ReadJoint ( spireJointView ); savefile->ReadJoint ( chestJointView ); currentPath.Restore ( savefile ); // Chain lightning mod savefile->ReadInt ( num ); chainLightning.SetNum ( num ); for ( i = 0; i < num; i ++ ) { chainLightning[i].Restore ( savefile ); } savefile->ReadVec3 ( chainLightningRange ); } /* ================ rvWeaponLightningGun::Think ================ */ void rvWeaponLightningGun::Think ( void ) { trace_t tr; rvWeapon::Think(); UpdateTubes(); // If no longer firing or out of ammo then nothing to do in the think if ( !wsfl.attack || !IsReady() || !AmmoAvailable() ) { if ( trailEffectView ) { trailEffectView->Stop ( ); trailEffectView = NULL; } currentPath.StopEffects(); StopChainLightning(); return; } // Cast a ray out to the lock range // RAVEN BEGIN // ddynerman: multiple clip worlds // jshepard: allow projectile hits gameLocal.TracePoint( owner, tr, playerViewOrigin, playerViewOrigin + playerViewAxis[0] * range, (MASK_SHOT_RENDERMODEL|CONTENTS_WATER|CONTENTS_PROJECTILE), owner ); // RAVEN END // Calculate the direction of the lightning effect using the barrel joint of the weapon // and the end point of the trace idVec3 origin; idMat3 axis; //fire from chest if show gun models is off. if( !cvarSystem->GetCVarBool("ui_showGun")) { GetGlobalJointTransform( true, chestJointView, origin, axis ); } else { GetGlobalJointTransform( true, barrelJointView, origin, axis ); } // Cache the target we are hitting currentPath.origin = tr.endpos; currentPath.normal = tr.c.normal; currentPath.target = gameLocal.entities[tr.c.entityNum]; UpdateChainLightning(); UpdateEffects( origin ); MuzzleFlash(); // Inflict damage on all targets being attacked if ( !gameLocal.isClient && gameLocal.time >= nextAttackTime ) { int i; float power = 1.0f; idVec3 dir; owner->inventory.UseAmmo( ammoType, ammoRequired ); dir = tr.endpos - origin; dir.Normalize ( ); nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE )); Attack ( currentPath.target, dir, power ); for ( i = 0; i < chainLightning.Num(); i ++, power *= 0.75f ) { Attack ( chainLightning[i].target, chainLightning[i].normal, power ); } statManager->WeaponFired( owner, owner->GetCurrentWeapon(), chainLightning.Num() + 1 ); } // Play the lightning crawl effect every so often when doing damage if ( gameLocal.time > nextCrawlTime ) { nextCrawlTime = gameLocal.time + SEC2MS(spawnArgs.GetFloat ( "crawlDelay", ".3" )); } } /* ================ rvWeaponLightningGun::Attack ================ */ void rvWeaponLightningGun::Attack ( idEntity* ent, const idVec3& dir, float power ) { // Double check if ( !ent || !ent->fl.takedamage ) { return; } // Start a lightning crawl effect every so often // we don't synchronize it, so let's not show it in multiplayer for a listen host. also fixes seeing it on the host from other instances if ( !gameLocal.isMultiplayer && gameLocal.time > nextCrawlTime ) { if ( ent->IsType( idActor::GetClassType() ) ) { rvClientCrawlEffect* effect; effect = new rvClientCrawlEffect( gameLocal.GetEffect( weaponDef->dict, "fx_crawl" ), ent, SEC2MS( spawnArgs.GetFloat ( "crawlTime", ".2" ) ) ); effect->Play( gameLocal.time, false ); } } // RAVEN BEGIN // mekberg: stats if( owner->IsType( idPlayer::GetClassType() ) && ent->IsType( idActor::GetClassType() ) && ent != owner && !((idPlayer*)owner)->pfl.dead ) { statManager->WeaponHit( (idActor*)owner, ent, owner->GetCurrentWeapon() ); } // RAVEN END ent->Damage( owner, owner, dir, spawnArgs.GetString ( "def_damage" ), power * owner->PowerUpModifier( PMOD_PROJECTILE_DAMAGE ), 0 ); } /* ================ rvWeaponLightningGun::UpdateChainLightning ================ */ void rvWeaponLightningGun::UpdateChainLightning ( void ) { int i; rvLightningPath* parent; rvLightningPath* path; idActor* target; // Chain lightning not enabled if ( !chainLightningRange[0] ) { return; } // Need to have a primary target that is not on the same team for chain lightning to work path = ¤tPath; target = dynamic_cast(path->target.GetEntity()); if ( !target || !target->health || target->team == owner->team ) { StopChainLightning ( ); return; } currentPath.target->fl.takedamage = false; // Look through the chain lightning list and remove any paths that are no longer valid due // to their range or visibility for ( i = 0; i < chainLightning.Num(); i ++ ) { parent = path; path = &chainLightning[i]; target = dynamic_cast(path->target.GetEntity()); // If the entity isnt valid anymore or is dead then remove it from the list if ( !target || target->health <= 0 || !target->fl.takedamage ) { path->StopEffects ( ); chainLightning.RemoveIndex ( i-- ); continue; } // Choose a destination origin the chain lightning path target path->origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; path->origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); path->normal = path->origin - parent->origin; path->normal.Normalize(); // Make sure the entity is still within range of its parent if ( (path->origin - parent->origin).LengthSqr ( ) > Square ( chainLightningRange[1] ) ) { path->StopEffects ( ); chainLightning.RemoveIndex ( i-- ); continue; } // Trace to make sure we can still hit them trace_t tr; // RAVEN BEGIN // ddynerman: multiple clip worlds gameLocal.TracePoint ( owner, tr, parent->origin, path->origin, MASK_SHOT_RENDERMODEL, parent->target ); // RAVEN END if ( tr.c.entityNum != target->entityNumber ) { path->StopEffects ( ); chainLightning.RemoveIndex ( i-- ); continue; } path->origin = tr.endpos; // Temporarily disable taking damage to flag this entity is used target->fl.takedamage = false; } // Start path at the end of the current path if ( chainLightning.Num () ) { path = &chainLightning[chainLightning.Num()-1]; } else { path = ¤tPath; } // Cap the number of chain lightning jumps while ( chainLightning.Num ( ) + 1 < LIGHTNINGGUN_MAX_PATHS ) { for ( target = aiManager.GetEnemyTeam ( (aiTeam_t)owner->team ); target; target = target->teamNode.Next() ) { // Must be a valid entity that takes damage to chain lightning too if ( !target || target->health <= 0 || !target->fl.takedamage ) { continue; } // Must be within starting chain path range if ( (target->GetPhysics()->GetOrigin() - path->target->GetPhysics()->GetOrigin()).LengthSqr() > Square ( chainLightningRange[0] ) ) { continue; } // Make sure we can trace to the target from the current path trace_t tr; idVec3 origin; origin = (target->GetPhysics()->GetAbsBounds().GetCenter() + target->GetEyePosition()) * 0.5f; origin += target->GetPhysics()->GetGravityNormal() * (gameLocal.random.RandomFloat() * 20.0f - 10.0f); // RAVEN BEGIN // ddynerman: multiple clip worlds gameLocal.TracePoint ( owner, tr, path->origin, origin, MASK_SHOT_RENDERMODEL, path->target ); // RAVEN END if ( tr.c.entityNum != target->entityNumber ) { continue; } path = &chainLightning.Alloc ( ); path->target = target; path->normal = tr.endpos - path->origin; path->normal.Normalize(); path->origin = tr.endpos; // Flag this entity to ensure it is skipped target->fl.takedamage = false; break; } // Found nothing? just break out early if ( !target ) { break; } } // Reset the take damage flag currentPath.target->fl.takedamage = true; for ( i = chainLightning.Num() - 1; i >= 0; i -- ) { chainLightning[i].target->fl.takedamage = true; } } /* ================ rvWeaponLightningGun::UpdateEffects ================ */ void rvWeaponLightningGun::UpdateEffects( const idVec3& origin ) { int i; rvLightningPath* parent; idVec3 dir; // Main path (world effects) currentPath.UpdateEffects ( origin, weaponDef->dict ); if ( currentPath.trailEffect ) { currentPath.trailEffect->GetRenderEffect()->suppressSurfaceInViewID = owner->entityNumber + 1; } // In view trail effect dir = currentPath.origin - origin; dir.Normalize(); if ( !trailEffectView ) { trailEffectView = gameLocal.PlayEffect ( gameLocal.GetEffect ( weaponDef->dict, "fx_trail" ), origin, dir.ToMat3(), true, currentPath.origin ); } else { trailEffectView->SetOrigin( origin ); trailEffectView->SetAxis( dir.ToMat3() ); trailEffectView->SetEndOrigin( currentPath.origin ); } if ( trailEffectView ) { trailEffectView->GetRenderEffect()->allowSurfaceInViewID = owner->entityNumber + 1;; } if ( !currentPath.target ) { return; } // Chain lightning effects parent = ¤tPath; for ( i = 0; i < chainLightning.Num(); i ++ ) { chainLightning[i].UpdateEffects( parent->origin, weaponDef->dict ); parent = &chainLightning[i]; } } /* ================ rvWeaponLightningGun::UpdateTrailEffect ================ */ void rvWeaponLightningGun::UpdateTrailEffect( rvClientEffectPtr& effect, const idVec3& start, const idVec3& end, bool view ) { idVec3 dir; dir = end - start; dir.Normalize(); if ( !effect ) { effect = gameLocal.PlayEffect( gameLocal.GetEffect( weaponDef->dict, view ? "fx_trail" : "fx_trail_world" ), start, dir.ToMat3(), true, end ); } else { effect->SetOrigin( start ); effect->SetAxis( dir.ToMat3() ); effect->SetEndOrigin( end ); } } /* ================ rvWeaponLightningGun::StopChainLightning ================ */ void rvWeaponLightningGun::StopChainLightning( void ) { int i; if ( !chainLightning.Num( ) ) { return; } for ( i = 0; i < chainLightning.Num(); i ++ ) { chainLightning[i].StopEffects( ); } chainLightning.Clear( ); } /* ================ rvWeaponLightningGun::ClientStale ================ */ void rvWeaponLightningGun::ClientStale( void ) { rvWeapon::ClientStale( ); if ( trailEffectView ) { trailEffectView->Stop(); trailEffectView->Event_Remove(); trailEffectView = NULL; } currentPath.StopEffects( ); } /* ================ rvWeaponLightningGun::PreSave ================ */ void rvWeaponLightningGun::PreSave( void ) { SetState ( "Idle", 4 ); StopSound( SND_CHANNEL_WEAPON, 0); StopSound( SND_CHANNEL_BODY, 0); StopSound( SND_CHANNEL_BODY2, 0); StopSound( SND_CHANNEL_BODY3, 0); StopSound( SND_CHANNEL_ITEM, 0); StopSound( SND_CHANNEL_ANY, false ); viewModel->StopSound( SND_CHANNEL_ANY, false ); viewModel->StopSound( SND_CHANNEL_BODY, 0); worldModel->StopSound( SND_CHANNEL_ANY, false ); worldModel->StopSound( SND_CHANNEL_BODY, 0); if ( trailEffectView ) { trailEffectView->Stop(); trailEffectView->Event_Remove(); trailEffectView = NULL; } for ( int i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { if ( tubeEffects[i] ) { tubeEffects[i]->Event_Remove(); } } currentPath.StopEffects( ); } /* ================ rvWeaponLightningGun::PostSave ================ */ void rvWeaponLightningGun::PostSave( void ) { //restore the humming PostEventMS( &EV_Lightninggun_RestoreHum, 10 ); } /* ================ rvWeaponLightningGun::UpdateTubes ================ */ void rvWeaponLightningGun::UpdateTubes( void ) { idAnimator* animator; animator = viewModel->GetAnimator ( ); assert ( animator ); int i; float ammo; ammo = AmmoAvailable ( ); for ( i = 0; i < LIGHTNINGGUN_NUM_TUBES; i ++ ) { float offset; if ( ammo > tubeThreshold * i ) { offset = tubeMaxOffset; if ( !tubeEffects[i] ) { tubeEffects[i] = viewModel->PlayEffect ( gameLocal.GetEffect( spawnArgs, "fx_tube" ), tubeJoints[i], vec3_origin, mat3_identity, true ); if( tubeEffects[i] ) { viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); } } } else { offset = 0; if ( tubeEffects[i] ) { tubeEffects[i]->Stop ( ); tubeEffects[i] = NULL; viewModel->StartSound ( "snd_tube", SND_CHANNEL_ANY, 0, false, NULL ); } } // Attenuate the tube effect to how much ammo is left in the tube if ( tubeEffects[i] ) { tubeEffects[i]->Attenuate ( (ammo - tubeThreshold * (float)i) / tubeThreshold ); } if ( offset > tubeOffsets[i].GetEndValue ( ) ) { float current; current = tubeOffsets[i].GetCurrentValue(gameLocal.time); tubeOffsets[i].Init ( gameLocal.time, (1.0f - (current/tubeMaxOffset)) * (float)tubeTime, current, offset ); } else if ( offset < tubeOffsets[i].GetEndValue ( ) ) { float current; current = tubeOffsets[i].GetCurrentValue(gameLocal.time); tubeOffsets[i].Init ( gameLocal.time, (current/tubeMaxOffset) * (float)tubeTime, current, offset ); } animator->SetJointPos ( tubeJoints[i], JOINTMOD_LOCAL, idVec3(tubeOffsets[i].GetCurrentValue(gameLocal.time),0,0) ); } } /* =============================================================================== States =============================================================================== */ CLASS_STATES_DECLARATION ( rvWeaponLightningGun ) STATE ( "Raise", rvWeaponLightningGun::State_Raise ) STATE ( "Lower", rvWeaponLightningGun::State_Lower ) STATE ( "Idle", rvWeaponLightningGun::State_Idle) STATE ( "Fire", rvWeaponLightningGun::State_Fire ) END_CLASS_STATES /* ================ rvWeaponLightningGun::State_Raise ================ */ stateResult_t rvWeaponLightningGun::State_Raise( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT, }; switch ( parms.stage ) { // Start the weapon raising case STAGE_INIT: SetStatus( WP_RISING ); viewModel->SetShaderParm( 6, 1 ); PlayAnim( ANIMCHANNEL_ALL, "raise", parms.blendFrames ); return SRESULT_STAGE( STAGE_WAIT ); // Wait for the weapon to finish raising and allow it to be cancelled by // lowering the weapon case STAGE_WAIT: if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { SetState( "Idle", 4 ); return SRESULT_DONE; } if ( wsfl.lowerWeapon ) { StopSound( SND_CHANNEL_BODY3, false ); SetState( "Lower", 4 ); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvWeaponLightningGun::State_Lower ================ */ stateResult_t rvWeaponLightningGun::State_Lower( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT, STAGE_WAITRAISE }; switch ( parms.stage ) { case STAGE_INIT: SetStatus( WP_LOWERING ); PlayAnim( ANIMCHANNEL_ALL, "putaway", parms.blendFrames ); return SRESULT_STAGE(STAGE_WAIT); case STAGE_WAIT: if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { SetStatus( WP_HOLSTERED ); return SRESULT_STAGE(STAGE_WAITRAISE); } return SRESULT_WAIT; case STAGE_WAITRAISE: if ( wsfl.raiseWeapon ) { SetState( "Raise", 0 ); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvWeaponLightningGun::State_Idle ================ */ stateResult_t rvWeaponLightningGun::State_Idle( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_WAIT, }; switch ( parms.stage ) { case STAGE_INIT: SetStatus( WP_READY ); PlayCycle( ANIMCHANNEL_ALL, "idle", parms.blendFrames ); StopSound( SND_CHANNEL_BODY3, false ); StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); return SRESULT_STAGE( STAGE_WAIT ); case STAGE_WAIT: if ( wsfl.lowerWeapon ) { StopSound( SND_CHANNEL_BODY3, false ); SetState( "Lower", 4 ); return SRESULT_DONE; } if ( wsfl.attack && gameLocal.time > nextAttackTime && AmmoAvailable ( ) ) { SetState( "Fire", 4 ); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvWeaponLightningGun::State_Fire ================ */ stateResult_t rvWeaponLightningGun::State_Fire( const stateParms_t& parms ) { enum { STAGE_INIT, STAGE_ATTACKLOOP, STAGE_DONE, STAGE_DONEWAIT }; switch ( parms.stage ) { case STAGE_INIT: StartSound( "snd_fire", SND_CHANNEL_WEAPON, 0, false, NULL ); StartSound( "snd_fire_stereo", SND_CHANNEL_ITEM, 0, false, NULL ); StartSound( "snd_fire_loop", SND_CHANNEL_BODY2, 0, false, NULL ); viewModel->SetShaderParm( 6, 0 ); viewModel->PlayEffect( "fx_spire", spireJointView, true ); viewModel->PlayEffect( "fx_flash", barrelJointView, true ); if ( worldModel && flashJointWorld != INVALID_JOINT ) { worldModel->PlayEffect( gameLocal.GetEffect( weaponDef->dict,"fx_flash_world"), flashJointWorld, vec3_origin, mat3_identity, true ); } PlayAnim( ANIMCHANNEL_ALL, "shoot_start", parms.blendFrames ); return SRESULT_STAGE( STAGE_ATTACKLOOP ); case STAGE_ATTACKLOOP: if ( !wsfl.attack || wsfl.lowerWeapon || !AmmoAvailable ( ) ) { return SRESULT_STAGE ( STAGE_DONE ); } if ( AnimDone( ANIMCHANNEL_ALL, 0 ) ) { PlayCycle( ANIMCHANNEL_ALL, "shoot_loop", 0 ); if ( !gameLocal.isMultiplayer && owner == gameLocal.GetLocalPlayer() ) { owner->playerView.SetShakeParms( MS2SEC(gameLocal.GetTime() + 500), 2.0f ); } } return SRESULT_WAIT; case STAGE_DONE: StopSound( SND_CHANNEL_BODY2, false ); viewModel->StopEffect( "fx_spire" ); viewModel->StopEffect( "fx_flash" ); if ( worldModel ) { worldModel->StopEffect( gameLocal.GetEffect( weaponDef->dict, "fx_flash_world" ) ); } viewModel->SetShaderParm( 6, 1 ); PlayAnim( ANIMCHANNEL_ALL, "shoot_end", 0 ); return SRESULT_STAGE( STAGE_DONEWAIT ); case STAGE_DONEWAIT: if ( AnimDone( ANIMCHANNEL_ALL, 4 ) ) { SetState( "Idle", 4 ); return SRESULT_DONE; } if ( !wsfl.lowerWeapon && wsfl.attack && AmmoAvailable ( ) ) { SetState( "Fire", 4 ); return SRESULT_DONE; } return SRESULT_WAIT; } return SRESULT_ERROR; } /* ================ rvWeaponLightningGun::Event_RestoreHum ================ */ void rvWeaponLightningGun::Event_RestoreHum ( void ) { StopSound( SND_CHANNEL_BODY3, false ); StartSound( "snd_idle_hum", SND_CHANNEL_BODY3, 0, false, NULL ); } /* =============================================================================== rvLightningPath =============================================================================== */ /* ================ rvLightningPath::StopEffects ================ */ void rvLightningPath::StopEffects( void ) { if ( trailEffect ) { trailEffect->Stop( ); trailEffect->Event_Remove( ); trailEffect = NULL; } if ( impactEffect ) { impactEffect->Stop( ); impactEffect->PostEventMS( &EV_Remove, 1000 ); impactEffect = NULL; } } /* ================ rvLightningPath::UpdateEffects ================ */ void rvLightningPath::UpdateEffects ( const idVec3& from, const idDict& dict ) { idVec3 dir; dir = origin - from; dir.Normalize(); // Trail effect if ( !trailEffect ) { trailEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_trail_world" ), from, dir.ToMat3(), true, origin ); } else { trailEffect->SetOrigin ( from ); trailEffect->SetAxis ( dir.ToMat3() ); trailEffect->SetEndOrigin ( origin ); } // Impact effect if ( !target || target.GetEntityNum ( ) == ENTITYNUM_NONE ) { if ( impactEffect ) { impactEffect->Stop ( ); impactEffect->PostEventMS( &EV_Remove, 1000 ); impactEffect = NULL; } } else { if ( !impactEffect ) { impactEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( dict, "fx_impact" ), origin, normal.ToMat3(), true ); } else { impactEffect->SetOrigin ( origin ); impactEffect->SetAxis ( normal.ToMat3 ( ) ); } } } /* ================ rvLightningPath::Save ================ */ void rvLightningPath::Save( idSaveGame* savefile ) const { target.Save( savefile ); savefile->WriteVec3( origin ); savefile->WriteVec3( normal ); trailEffect.Save( savefile ); impactEffect.Save( savefile ); } /* ================ rvLightningPath::Restore ================ */ void rvLightningPath::Restore( idRestoreGame* savefile ) { target.Restore( savefile ); savefile->ReadVec3( origin ); savefile->ReadVec3( normal ); trailEffect.Restore( savefile ); impactEffect.Restore( savefile ); }