583 lines
14 KiB
C++
583 lines
14 KiB
C++
#include "../../idlib/precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#include "../Game_local.h"
|
|
#include "../Weapon.h"
|
|
#include "../client/ClientEffect.h"
|
|
|
|
#ifndef __GAME_PROJECTILE_H__
|
|
#include "../Projectile.h"
|
|
#endif
|
|
|
|
class rvWeaponRocketLauncher : public rvWeapon {
|
|
public:
|
|
|
|
CLASS_PROTOTYPE( rvWeaponRocketLauncher );
|
|
|
|
rvWeaponRocketLauncher ( void );
|
|
~rvWeaponRocketLauncher ( void );
|
|
|
|
virtual void Spawn ( void );
|
|
virtual void Think ( void );
|
|
|
|
void Save( idSaveGame *saveFile ) const;
|
|
void Restore( idRestoreGame *saveFile );
|
|
void PreSave ( void );
|
|
void PostSave ( void );
|
|
|
|
|
|
#ifdef _XENON
|
|
virtual bool AllowAutoAim ( void ) const { return false; }
|
|
#endif
|
|
|
|
protected:
|
|
|
|
virtual void OnLaunchProjectile ( idProjectile* proj );
|
|
|
|
void SetRocketState ( const char* state, int blendFrames );
|
|
|
|
rvClientEntityPtr<rvClientEffect> guideEffect;
|
|
idList< idEntityPtr<idEntity> > guideEnts;
|
|
float guideSpeedSlow;
|
|
float guideSpeedFast;
|
|
float guideRange;
|
|
float guideAccelTime;
|
|
|
|
rvStateThread rocketThread;
|
|
|
|
float reloadRate;
|
|
|
|
bool idleEmpty;
|
|
|
|
private:
|
|
|
|
stateResult_t State_Idle ( const stateParms_t& parms );
|
|
stateResult_t State_Fire ( const stateParms_t& parms );
|
|
stateResult_t State_Raise ( const stateParms_t& parms );
|
|
stateResult_t State_Lower ( const stateParms_t& parms );
|
|
|
|
stateResult_t State_Rocket_Idle ( const stateParms_t& parms );
|
|
stateResult_t State_Rocket_Reload ( const stateParms_t& parms );
|
|
|
|
stateResult_t Frame_AddToClip ( const stateParms_t& parms );
|
|
|
|
CLASS_STATES_PROTOTYPE ( rvWeaponRocketLauncher );
|
|
};
|
|
|
|
CLASS_DECLARATION( rvWeapon, rvWeaponRocketLauncher )
|
|
END_CLASS
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::rvWeaponRocketLauncher
|
|
================
|
|
*/
|
|
rvWeaponRocketLauncher::rvWeaponRocketLauncher ( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::~rvWeaponRocketLauncher
|
|
================
|
|
*/
|
|
rvWeaponRocketLauncher::~rvWeaponRocketLauncher ( void ) {
|
|
if ( guideEffect ) {
|
|
guideEffect->Stop();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::Spawn
|
|
================
|
|
*/
|
|
void rvWeaponRocketLauncher::Spawn ( void ) {
|
|
float f;
|
|
|
|
idleEmpty = false;
|
|
|
|
spawnArgs.GetFloat ( "lockRange", "0", guideRange );
|
|
|
|
spawnArgs.GetFloat ( "lockSlowdown", ".25", f );
|
|
attackDict.GetFloat ( "speed", "0", guideSpeedFast );
|
|
guideSpeedSlow = guideSpeedFast * f;
|
|
|
|
reloadRate = SEC2MS ( spawnArgs.GetFloat ( "reloadRate", ".8" ) );
|
|
|
|
guideAccelTime = SEC2MS ( spawnArgs.GetFloat ( "lockAccelTime", ".25" ) );
|
|
|
|
// Start rocket thread
|
|
rocketThread.SetName ( viewModel->GetName ( ) );
|
|
rocketThread.SetOwner ( this );
|
|
|
|
// Adjust reload animations to match the fire rate
|
|
idAnim* anim;
|
|
int animNum;
|
|
float rate;
|
|
animNum = viewModel->GetAnimator()->GetAnim ( "reload" );
|
|
if ( animNum ) {
|
|
anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum );
|
|
rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" ));
|
|
anim->SetPlaybackRate ( rate );
|
|
}
|
|
|
|
animNum = viewModel->GetAnimator()->GetAnim ( "reload_empty" );
|
|
if ( animNum ) {
|
|
anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum );
|
|
rate = (float)anim->Length() / (float)SEC2MS(spawnArgs.GetFloat ( "reloadRate", ".8" ));
|
|
anim->SetPlaybackRate ( rate );
|
|
}
|
|
|
|
SetState ( "Raise", 0 );
|
|
SetRocketState ( "Rocket_Idle", 0 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::Think
|
|
================
|
|
*/
|
|
void rvWeaponRocketLauncher::Think ( void ) {
|
|
trace_t tr;
|
|
int i;
|
|
|
|
rocketThread.Execute ( );
|
|
|
|
// Let the real weapon think first
|
|
rvWeapon::Think ( );
|
|
|
|
// IF no guide range is set then we dont have the mod yet
|
|
if ( !guideRange ) {
|
|
return;
|
|
}
|
|
|
|
if ( !wsfl.zoom ) {
|
|
if ( guideEffect ) {
|
|
guideEffect->Stop();
|
|
guideEffect = NULL;
|
|
}
|
|
|
|
for ( i = guideEnts.Num() - 1; i >= 0; i -- ) {
|
|
idGuidedProjectile* proj = static_cast<idGuidedProjectile*>(guideEnts[i].GetEntity());
|
|
if ( !proj || proj->IsHidden ( ) ) {
|
|
guideEnts.RemoveIndex ( i );
|
|
continue;
|
|
}
|
|
|
|
// If the rocket is still guiding then stop the guide and slow it down
|
|
if ( proj->GetGuideType ( ) != idGuidedProjectile::GUIDE_NONE ) {
|
|
proj->CancelGuide ( );
|
|
proj->SetSpeed ( guideSpeedFast, (1.0f - (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow)) * guideAccelTime );
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// Cast a ray out to the lock range
|
|
// RAVEN BEGIN
|
|
// ddynerman: multiple clip worlds
|
|
gameLocal.TracePoint( owner, tr,
|
|
playerViewOrigin,
|
|
playerViewOrigin + playerViewAxis[0] * guideRange,
|
|
MASK_SHOT_RENDERMODEL, owner );
|
|
// RAVEN END
|
|
|
|
for ( i = guideEnts.Num() - 1; i >= 0; i -- ) {
|
|
idGuidedProjectile* proj = static_cast<idGuidedProjectile*>(guideEnts[i].GetEntity());
|
|
if ( !proj || proj->IsHidden() ) {
|
|
guideEnts.RemoveIndex ( i );
|
|
continue;
|
|
}
|
|
|
|
// If the rocket isnt guiding yet then adjust its speed back to normal
|
|
if ( proj->GetGuideType ( ) == idGuidedProjectile::GUIDE_NONE ) {
|
|
proj->SetSpeed ( guideSpeedSlow, (proj->GetSpeed ( ) - guideSpeedSlow) / (guideSpeedFast - guideSpeedSlow) * guideAccelTime );
|
|
}
|
|
proj->GuideTo ( tr.endpos );
|
|
}
|
|
|
|
if ( !guideEffect ) {
|
|
guideEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_guide" ), tr.endpos, tr.c.normal.ToMat3(), true, vec3_origin, true );
|
|
} else {
|
|
guideEffect->SetOrigin ( tr.endpos );
|
|
guideEffect->SetAxis ( tr.c.normal.ToMat3() );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::OnLaunchProjectile
|
|
================
|
|
*/
|
|
void rvWeaponRocketLauncher::OnLaunchProjectile ( idProjectile* proj ) {
|
|
rvWeapon::OnLaunchProjectile(proj);
|
|
|
|
// Double check that its actually a guided projectile
|
|
if ( !proj || !proj->IsType ( idGuidedProjectile::GetClassType() ) ) {
|
|
return;
|
|
}
|
|
|
|
// Launch the projectile
|
|
idEntityPtr<idEntity> ptr;
|
|
ptr = proj;
|
|
guideEnts.Append ( ptr );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::SetRocketState
|
|
================
|
|
*/
|
|
void rvWeaponRocketLauncher::SetRocketState ( const char* state, int blendFrames ) {
|
|
rocketThread.SetState ( state, blendFrames );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvWeaponRocketLauncher::Save
|
|
=====================
|
|
*/
|
|
void rvWeaponRocketLauncher::Save( idSaveGame *saveFile ) const {
|
|
saveFile->WriteObject( guideEffect );
|
|
|
|
idEntity* ent = NULL;
|
|
saveFile->WriteInt( guideEnts.Num() );
|
|
for( int ix = 0; ix < guideEnts.Num(); ++ix ) {
|
|
ent = guideEnts[ ix ].GetEntity();
|
|
if( ent ) {
|
|
saveFile->WriteObject( ent );
|
|
}
|
|
}
|
|
|
|
saveFile->WriteFloat( guideSpeedSlow );
|
|
saveFile->WriteFloat( guideSpeedFast );
|
|
saveFile->WriteFloat( guideRange );
|
|
saveFile->WriteFloat( guideAccelTime );
|
|
|
|
saveFile->WriteFloat ( reloadRate );
|
|
|
|
rocketThread.Save( saveFile );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
rvWeaponRocketLauncher::Restore
|
|
=====================
|
|
*/
|
|
void rvWeaponRocketLauncher::Restore( idRestoreGame *saveFile ) {
|
|
int numEnts = 0;
|
|
idEntity* ent = NULL;
|
|
rvClientEffect* clientEffect = NULL;
|
|
|
|
saveFile->ReadObject( reinterpret_cast<idClass *&>(clientEffect) );
|
|
guideEffect = clientEffect;
|
|
|
|
saveFile->ReadInt( numEnts );
|
|
guideEnts.Clear();
|
|
guideEnts.SetNum( numEnts );
|
|
for( int ix = 0; ix < numEnts; ++ix ) {
|
|
saveFile->ReadObject( reinterpret_cast<idClass *&>(ent) );
|
|
guideEnts[ ix ] = ent;
|
|
}
|
|
|
|
saveFile->ReadFloat( guideSpeedSlow );
|
|
saveFile->ReadFloat( guideSpeedFast );
|
|
saveFile->ReadFloat( guideRange );
|
|
saveFile->ReadFloat( guideAccelTime );
|
|
|
|
saveFile->ReadFloat ( reloadRate );
|
|
|
|
rocketThread.Restore( saveFile, this );
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::PreSave
|
|
================
|
|
*/
|
|
void rvWeaponRocketLauncher::PreSave ( void ) {
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::PostSave
|
|
================
|
|
*/
|
|
void rvWeaponRocketLauncher::PostSave ( void ) {
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
States
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
CLASS_STATES_DECLARATION ( rvWeaponRocketLauncher )
|
|
STATE ( "Idle", rvWeaponRocketLauncher::State_Idle)
|
|
STATE ( "Fire", rvWeaponRocketLauncher::State_Fire )
|
|
STATE ( "Raise", rvWeaponRocketLauncher::State_Raise )
|
|
STATE ( "Lower", rvWeaponRocketLauncher::State_Lower )
|
|
|
|
STATE ( "Rocket_Idle", rvWeaponRocketLauncher::State_Rocket_Idle )
|
|
STATE ( "Rocket_Reload", rvWeaponRocketLauncher::State_Rocket_Reload )
|
|
|
|
STATE ( "AddToClip", rvWeaponRocketLauncher::Frame_AddToClip )
|
|
END_CLASS_STATES
|
|
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::State_Raise
|
|
|
|
Raise the weapon
|
|
================
|
|
*/
|
|
stateResult_t rvWeaponRocketLauncher::State_Raise ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_INIT,
|
|
STAGE_WAIT,
|
|
};
|
|
switch ( parms.stage ) {
|
|
// Start the weapon raising
|
|
case STAGE_INIT:
|
|
SetStatus ( WP_RISING );
|
|
PlayAnim( ANIMCHANNEL_LEGS, "raise", 0 );
|
|
return SRESULT_STAGE ( STAGE_WAIT );
|
|
|
|
case STAGE_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) {
|
|
SetState ( "Idle", 4 );
|
|
return SRESULT_DONE;
|
|
}
|
|
if ( wsfl.lowerWeapon ) {
|
|
SetState ( "Lower", 4 );
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::State_Lower
|
|
|
|
Lower the weapon
|
|
================
|
|
*/
|
|
stateResult_t rvWeaponRocketLauncher::State_Lower ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_INIT,
|
|
STAGE_WAIT,
|
|
STAGE_WAITRAISE
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_INIT:
|
|
SetStatus ( WP_LOWERING );
|
|
PlayAnim ( ANIMCHANNEL_LEGS, "putaway", parms.blendFrames );
|
|
return SRESULT_STAGE(STAGE_WAIT);
|
|
|
|
case STAGE_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_LEGS, 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;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::State_Idle
|
|
================
|
|
*/
|
|
stateResult_t rvWeaponRocketLauncher::State_Idle( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_INIT,
|
|
STAGE_WAIT,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_INIT:
|
|
if ( !AmmoAvailable ( ) ) {
|
|
SetStatus ( WP_OUTOFAMMO );
|
|
} else {
|
|
SetStatus ( WP_READY );
|
|
}
|
|
|
|
PlayCycle( ANIMCHANNEL_LEGS, "idle", parms.blendFrames );
|
|
return SRESULT_STAGE ( STAGE_WAIT );
|
|
|
|
case STAGE_WAIT:
|
|
if ( wsfl.lowerWeapon ) {
|
|
SetState ( "Lower", 4 );
|
|
return SRESULT_DONE;
|
|
}
|
|
if ( gameLocal.time > nextAttackTime && wsfl.attack && ( gameLocal.isClient || AmmoInClip ( ) ) ) {
|
|
SetState ( "Fire", 2 );
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::State_Fire
|
|
================
|
|
*/
|
|
stateResult_t rvWeaponRocketLauncher::State_Fire ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_INIT,
|
|
STAGE_WAIT,
|
|
};
|
|
switch ( parms.stage ) {
|
|
case STAGE_INIT:
|
|
nextAttackTime = gameLocal.time + (fireRate * owner->PowerUpModifier ( PMOD_FIRERATE ));
|
|
Attack ( false, 1, spread, 0, 1.0f );
|
|
PlayAnim ( ANIMCHANNEL_LEGS, "fire", parms.blendFrames );
|
|
return SRESULT_STAGE ( STAGE_WAIT );
|
|
|
|
case STAGE_WAIT:
|
|
if ( wsfl.attack && gameLocal.time >= nextAttackTime && ( gameLocal.isClient || AmmoInClip ( ) ) && !wsfl.lowerWeapon ) {
|
|
SetState ( "Fire", 0 );
|
|
return SRESULT_DONE;
|
|
}
|
|
if ( gameLocal.time > nextAttackTime && AnimDone ( ANIMCHANNEL_LEGS, 4 ) ) {
|
|
SetState ( "Idle", 4 );
|
|
return SRESULT_DONE;
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::State_Rocket_Idle
|
|
================
|
|
*/
|
|
stateResult_t rvWeaponRocketLauncher::State_Rocket_Idle ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_INIT,
|
|
STAGE_WAIT,
|
|
STAGE_WAITEMPTY,
|
|
};
|
|
|
|
switch ( parms.stage ) {
|
|
case STAGE_INIT:
|
|
if ( AmmoAvailable ( ) <= AmmoInClip() ) {
|
|
PlayAnim( ANIMCHANNEL_TORSO, "idle_empty", parms.blendFrames );
|
|
idleEmpty = true;
|
|
} else {
|
|
PlayAnim( ANIMCHANNEL_TORSO, "idle", parms.blendFrames );
|
|
}
|
|
return SRESULT_STAGE ( STAGE_WAIT );
|
|
|
|
case STAGE_WAIT:
|
|
if ( AmmoAvailable ( ) > AmmoInClip() ) {
|
|
if ( idleEmpty ) {
|
|
SetRocketState ( "Rocket_Reload", 0 );
|
|
return SRESULT_DONE;
|
|
} else if ( ClipSize ( ) > 1 ) {
|
|
if ( gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) ) {
|
|
if ( !AmmoInClip() || !wsfl.attack ) {
|
|
SetRocketState ( "Rocket_Reload", 0 );
|
|
return SRESULT_DONE;
|
|
}
|
|
}
|
|
} else {
|
|
if ( AmmoInClip ( ) == 0 ) {
|
|
SetRocketState ( "Rocket_Reload", 0 );
|
|
return SRESULT_DONE;
|
|
}
|
|
}
|
|
}
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::State_Rocket_Reload
|
|
================
|
|
*/
|
|
stateResult_t rvWeaponRocketLauncher::State_Rocket_Reload ( const stateParms_t& parms ) {
|
|
enum {
|
|
STAGE_INIT,
|
|
STAGE_WAIT,
|
|
};
|
|
|
|
switch ( parms.stage ) {
|
|
case STAGE_INIT: {
|
|
const char* animName;
|
|
int animNum;
|
|
|
|
if ( idleEmpty ) {
|
|
animName = "ammo_pickup";
|
|
idleEmpty = false;
|
|
} else if ( AmmoAvailable ( ) == AmmoInClip( ) + 1 ) {
|
|
animName = "reload_empty";
|
|
} else {
|
|
animName = "reload";
|
|
}
|
|
|
|
animNum = viewModel->GetAnimator()->GetAnim ( animName );
|
|
if ( animNum ) {
|
|
idAnim* anim;
|
|
anim = (idAnim*)viewModel->GetAnimator()->GetAnim ( animNum );
|
|
anim->SetPlaybackRate ( (float)anim->Length() / (reloadRate * owner->PowerUpModifier ( PMOD_FIRERATE )) );
|
|
}
|
|
|
|
PlayAnim( ANIMCHANNEL_TORSO, animName, parms.blendFrames );
|
|
|
|
return SRESULT_STAGE ( STAGE_WAIT );
|
|
}
|
|
|
|
case STAGE_WAIT:
|
|
if ( AnimDone ( ANIMCHANNEL_TORSO, 0 ) ) {
|
|
if ( !wsfl.attack && gameLocal.time > nextAttackTime && AmmoInClip ( ) < ClipSize( ) && AmmoAvailable() > AmmoInClip() ) {
|
|
SetRocketState ( "Rocket_Reload", 0 );
|
|
} else {
|
|
SetRocketState ( "Rocket_Idle", 0 );
|
|
}
|
|
return SRESULT_DONE;
|
|
}
|
|
/*
|
|
if ( gameLocal.isMultiplayer && gameLocal.time > nextAttackTime && wsfl.attack ) {
|
|
if ( AmmoInClip ( ) == 0 )
|
|
{
|
|
AddToClip ( ClipSize() );
|
|
}
|
|
SetRocketState ( "Rocket_Idle", 0 );
|
|
return SRESULT_DONE;
|
|
}
|
|
*/
|
|
return SRESULT_WAIT;
|
|
}
|
|
return SRESULT_ERROR;
|
|
}
|
|
|
|
/*
|
|
================
|
|
rvWeaponRocketLauncher::Frame_AddToClip
|
|
================
|
|
*/
|
|
stateResult_t rvWeaponRocketLauncher::Frame_AddToClip ( const stateParms_t& parms ) {
|
|
AddToClip ( 1 );
|
|
return SRESULT_OK;
|
|
}
|
|
|