#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( "<lightninggunRestoreHum>", "" );

class rvLightningPath {
public:
	idEntityPtr<idEntity>	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 );
	virtual void			ClientUnstale( 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<rvClientEffect>	tubeEffects[LIGHTNINGGUN_NUM_TUBES];
	idInterpolate<float>				tubeOffsets[LIGHTNINGGUN_NUM_TUBES];
	jointHandle_t						tubeJoints[LIGHTNINGGUN_NUM_TUBES];
	float								tubeMaxOffset;
	float								tubeThreshold;
	int									tubeTime;

	rvClientEntityPtr<rvClientEffect>	trailEffectView;

	int									nextCrawlTime;

	float								range;
	jointHandle_t						spireJointView;
	jointHandle_t						chestJointView;
	

	rvLightningPath						currentPath;
	
	// Chain lightning mod
	idList<rvLightningPath>				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 );
	if ( num < 0 || num > MAX_SAVEGAME_CHAIN_LIGHTNING_TARGETS ) {
		savefile->Error( "rvWeaponLightningGun::Restore: invalid chain lightning count %d", 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   = &currentPath;
	target = dynamic_cast<idActor*>(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<idActor*>(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 = &currentPath;
	}
	
	// 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 = &currentPath;
	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 );
			DisableViewEffectWeaponDepthHack( 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 );
}

/*
================
rvWeaponLightningGun::ClientUnStale
================
*/
void rvWeaponLightningGun::ClientUnstale( void ) {
	Event_RestoreHum();
}

/*
===============================================================================

	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 );
}
