973 lines
26 KiB
C++
973 lines
26 KiB
C++
#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 );
|
|
|
|
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 );
|
|
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<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 = ¤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 );
|
|
}
|