summaryrefslogtreecommitdiff
path: root/game/shared/tf/halloween/eyeball_boss/teleport_vortex.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/shared/tf/halloween/eyeball_boss/teleport_vortex.cpp')
-rw-r--r--game/shared/tf/halloween/eyeball_boss/teleport_vortex.cpp611
1 files changed, 611 insertions, 0 deletions
diff --git a/game/shared/tf/halloween/eyeball_boss/teleport_vortex.cpp b/game/shared/tf/halloween/eyeball_boss/teleport_vortex.cpp
new file mode 100644
index 0000000..53d720d
--- /dev/null
+++ b/game/shared/tf/halloween/eyeball_boss/teleport_vortex.cpp
@@ -0,0 +1,611 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_gamerules.h"
+#include "teleport_vortex.h"
+
+//-----------------------------------------------------------------------------
+
+#ifdef CLIENT_DLL
+
+ #include "c_tf_fx.h"
+
+ inline float SCurve( float t )
+ {
+ t = clamp( t, 0.0f, 1.0f );
+ return t * t * (3 - 2*t);
+ }
+
+#else
+
+ #include "tf_fx.h"
+ #include "tf_team.h"
+ #include "tf_weapon_sniperrifle.h"
+
+#endif
+
+//-----------------------------------------------------------------------------
+
+ConVar vortex_float_osc_speed( "vortex_float_osc_speed", "2.0", FCVAR_HIDDEN | FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar vortex_float_amp( "vortex_float_amp", "5.0", FCVAR_HIDDEN | FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar vortex_fade_fraction_denom( "vortex_fade_fraction_denom", "10.0", FCVAR_HIDDEN | FCVAR_CHEAT | FCVAR_REPLICATED );
+ConVar vortex_book_offset( "vortex_book_offset", "5.0", FCVAR_HIDDEN | FCVAR_CHEAT | FCVAR_REPLICATED );
+
+//-----------------------------------------------------------------------------
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TeleportVortex, DT_TeleportVortex )
+
+BEGIN_NETWORK_TABLE( CTeleportVortex, DT_TeleportVortex )
+ #if defined( CLIENT_DLL )
+ RecvPropInt( RECVINFO( m_iState ) ),
+ #else
+ SendPropInt( SENDINFO( m_iState ), 4, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
+ #endif
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( teleport_vortex, CTeleportVortex );
+
+BEGIN_DATADESC( CTeleportVortex )
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+
+#define VORTEX_PARTICLE_EFFECT_EYEBALL_MOVED "eyeboss_tp_vortex"
+#define VORTEX_PARTICLE_EFFECT_EYEBALL_DIED "eyeboss_aura_angry"
+
+#define VORTEX_BOOK_MODEL "models/props_halloween/bombonomicon.mdl"
+
+#define VORTEX_SOUND_EYEBALL_MOVED "Halloween.TeleportVortex.EyeballMovedVortex"
+#define VORTEX_SOUND_EYEBALL_DIED "Halloween.TeleportVortex.EyeballDiedVortex"
+#define VORTEX_SOUND_BOOK_SPAWN "Halloween.TeleportVortex.BookSpawn"
+#define VORTEX_SOUND_BOOK_EXIT "Halloween.TeleportVortex.BookExit"
+
+#define VORTEX_OPEN_OPEN_ANIM "flip_stimulated"
+
+//-----------------------------------------------------------------------------
+
+CTeleportVortex::CTeleportVortex()
+#ifdef GAME_DLL
+: m_iAutoSetupVortex( -1 )
+#endif
+{
+}
+
+CTeleportVortex::~CTeleportVortex()
+{
+#ifdef CLIENT_DLL
+ DestroyParticleEffect();
+#endif
+}
+
+#ifdef CLIENT_DLL
+void CTeleportVortex::DestroyParticleEffect()
+{
+ if ( m_pVortexEffect )
+ {
+ ParticleProp()->StopEmission( m_pVortexEffect );
+ m_pVortexEffect = NULL;
+ }
+}
+#else
+bool CTeleportVortex::KeyValue( const char *szKeyname, const char *szValue )
+{
+ if ( !V_strnicmp( szKeyname, "type", 4 ) )
+ {
+ m_iAutoSetupVortex = atoi( szValue );
+ }
+ else
+ {
+ return BaseClass::KeyValue( szKeyname, szValue );
+ }
+
+ return true;
+}
+#endif
+
+void CTeleportVortex::Spawn( void )
+{
+ Precache();
+ BaseClass::Spawn();
+
+ AddEffects( EF_NODRAW );
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_BBOX );
+ CollisionProp()->SetCollisionBounds( Vector( -50, -50, -50 ), Vector( 50, 50, 50 ) );
+ SetModelName( NULL_STRING );
+ SetSolidFlags( FSOLID_TRIGGER );
+ SetCollisionGroup( COLLISION_GROUP_WEAPON );
+ SetRenderMode( kRenderTransAlpha );
+ SetRenderColorA( 255 );
+ UseClientSideAnimation();
+
+ m_lifeTimer.Start( 5.0f );
+
+#ifdef CLIENT_DLL
+ m_flScale = 1.0f;
+ m_iOldState = VORTEXSTATE_INACTIVE;
+ m_pVortexEffect = NULL;
+
+ ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS );
+
+ AddToLeafSystem( RENDER_GROUP_TWOPASS );
+#else
+ // Default to purgatory
+ m_pszWhere = "spawn_purgatory";
+
+ SetThink( &CTeleportVortex::VortexThink );
+ SetNextThink( gpGlobals->curtime );
+
+ m_nSoundCounter = 0;
+
+ m_bSplitTeam = false;
+
+ if ( m_iAutoSetupVortex >= 0 )
+ {
+ SetupVortex( m_iAutoSetupVortex != 0 );
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+
+void CTeleportVortex::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheModel( VORTEX_BOOK_MODEL );
+
+ // We deliberately allow late precaches here.
+ bool bAllowPrecache = CBaseAnimating::IsPrecacheAllowed();
+ CBaseAnimating::SetAllowPrecache( true );
+
+#if GAME_DLL
+ PrecacheScriptSound( VORTEX_SOUND_EYEBALL_MOVED );
+ PrecacheScriptSound( VORTEX_SOUND_EYEBALL_DIED );
+ PrecacheScriptSound( VORTEX_SOUND_BOOK_SPAWN );
+ PrecacheScriptSound( VORTEX_SOUND_BOOK_EXIT );
+#endif
+
+ PrecacheParticleSystem( VORTEX_PARTICLE_EFFECT_EYEBALL_MOVED );
+ PrecacheParticleSystem( VORTEX_PARTICLE_EFFECT_EYEBALL_DIED );
+
+ CBaseAnimating::SetAllowPrecache( bAllowPrecache );
+}
+
+//-----------------------------------------------------------------------------
+
+#ifdef GAME_DLL
+
+void CTeleportVortex::Touch( CBaseEntity *pOther )
+{
+ BaseClass::Touch( pOther );
+
+ if ( pOther && pOther->IsPlayer() )
+ {
+ if ( m_bSplitTeam )
+ {
+ const char* pszTeam = pOther->GetTeamNumber() == TF_TEAM_RED ? "_red" : "_blue";
+ SendPlayerToTheUnderworld( ToTFPlayer( pOther ), CFmtStr( "%s%s", m_pszWhere.Get(), pszTeam ) );
+ }
+ else
+ {
+ SendPlayerToTheUnderworld( ToTFPlayer( pOther ), m_pszWhere );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+
+int CTeleportVortex::UpdateTransmitState( void )
+{
+ return SetTransmitState( FL_EDICT_PVSCHECK );
+}
+
+//-----------------------------------------------------------------------------
+
+void CTeleportVortex::StartTouch( CBaseEntity *pOther )
+{
+ CBaseAnimating::StartTouch( pOther );
+}
+
+//-----------------------------------------------------------------------------
+
+void CTeleportVortex::SetupVortex( bool bIsDeathVortex, bool bSplitTeam /*= false*/ )
+{
+ m_bSplitTeam = bSplitTeam;
+ if ( bIsDeathVortex )
+ {
+ m_iState = VORTEXSTATE_ACTIVE_EYEBALL_DIED;
+ m_pszWhere = "spawn_loot";
+ RemoveEffects( EF_NODRAW );
+ SetModel( VORTEX_BOOK_MODEL );
+ }
+ else
+ {
+ m_iState = VORTEXSTATE_ACTIVE_EYEBALL_MOVED;
+ m_pszWhere = "spawn_purgatory";
+ AddEffects( EF_NODRAW );
+ }
+}
+
+void CTeleportVortex::VortexThink()
+{
+ if ( m_lifeTimer.IsElapsed() )
+ {
+ UTIL_Remove( this );
+ }
+ else
+ {
+ StudioFrameAdvance();
+
+ if ( m_iState == VORTEXSTATE_ACTIVE_EYEBALL_DIED )
+ {
+ if ( m_nSoundCounter == 0 && ShouldDoBookRampIn() )
+ {
+ EmitSound( VORTEX_SOUND_BOOK_SPAWN );
+ ++m_nSoundCounter;
+ }
+ else if ( m_nSoundCounter == 1 && ShouldDoBookRampOut() )
+ {
+ EmitSound( VORTEX_SOUND_BOOK_EXIT );
+ ++m_nSoundCounter;
+ }
+ }
+
+ SetNextThink( gpGlobals->curtime );
+ }
+
+ // suck nearby players into the vortex
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS );
+ CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS );
+
+ const float suctionRange = 500.0f;
+
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ CTFPlayer *player = playerVector[i];
+
+ if ( player->GetFlags() & FL_ONGROUND )
+ {
+ // not airborne
+ continue;
+ }
+
+ Vector toVortex = WorldSpaceCenter() - player->WorldSpaceCenter();
+ float range = toVortex.NormalizeInPlace();
+
+ if ( range > suctionRange )
+ {
+ // too far
+ continue;
+ }
+
+ if ( player->IsLookingTowards( WorldSpaceCenter() ) )
+ {
+ if ( player->IsLineOfSightClear( WorldSpaceCenter(), CBaseCombatCharacter::IGNORE_ACTORS, player ) )
+ {
+ player->ApplyAbsVelocityImpulse( 30.0f * toVortex );
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+void SendPlayerToTheUnderworld( CTFPlayer *teleportingPlayer, const char *where )
+{
+ if ( !teleportingPlayer )
+ {
+ return;
+ }
+
+ CUtlVector< CBaseEntity * > spawnVector;
+
+ CBaseEntity *spawnPoint = NULL;
+ while( ( spawnPoint = gEntList.FindEntityByClassname( spawnPoint, "info_target" ) ) != NULL )
+ {
+ if ( FStrEq( STRING( spawnPoint->GetEntityName() ), where ) )
+ {
+ spawnVector.AddToTail( spawnPoint );
+ }
+ }
+
+ if ( spawnVector.Count() == 0 )
+ {
+ Warning( "SendPlayerToTheUnderworld: No info_target entities named '%s' found!\n", where );
+ return;
+ }
+
+ // collect enemies that could block our spawning
+ CUtlVector< CTFPlayer * > enemyVector;
+ CollectPlayers( &enemyVector, GetEnemyTeam( teleportingPlayer->GetTeamNumber() ), COLLECT_ONLY_LIVING_PLAYERS );
+
+ const float nearRange = 25.0f;
+
+ // collect spots without players overlapping them
+ CUtlVector< CBaseEntity * > openSpawnVector;
+ for( int i=0; i<spawnVector.Count(); ++i )
+ {
+ int p;
+
+ for( p=0; p<enemyVector.Count(); ++p )
+ {
+ if ( ( spawnVector[i]->GetAbsOrigin() - enemyVector[p]->GetAbsOrigin() ).IsLengthLessThan( nearRange ) )
+ {
+ // a player is occupying this spawn
+ break;
+ }
+ }
+
+ if ( p == enemyVector.Count() )
+ {
+ // no players are near this spawn point
+ openSpawnVector.AddToTail( spawnVector[i] );
+ }
+ }
+
+ CBaseEntity *teleportDestination = NULL;
+
+ if ( openSpawnVector.Count() == 0 )
+ {
+ // there are no free spawns - pick one and telefrag enemies standing there
+ int which = RandomInt( 0, spawnVector.Count()-1 );
+
+ teleportDestination = spawnVector[ which ];
+
+ for( int p=0; p<enemyVector.Count(); ++p )
+ {
+ if ( ( teleportDestination->GetAbsOrigin() - enemyVector[p]->GetAbsOrigin() ).IsLengthLessThan( nearRange ) )
+ {
+ // telefrag!
+ enemyVector[p]->TakeDamage( CTakeDamageInfo( teleportingPlayer, teleportingPlayer, 1000, DMG_CRUSH, TF_DMG_CUSTOM_TELEFRAG ) );
+ }
+ }
+ }
+ else
+ {
+ // pick an open destination at random
+ int which = RandomInt( 0, openSpawnVector.Count()-1 );
+
+ teleportDestination = openSpawnVector[ which ];
+ }
+
+ if ( teleportDestination )
+ {
+ if ( teleportingPlayer->GetTeam() )
+ {
+ UTIL_LogPrintf( "HALLOWEEN: \"%s<%i><%s><%s>\" purgatory_teleport \"%s\"\n",
+ teleportingPlayer->GetPlayerName(),
+ teleportingPlayer->GetUserID(),
+ teleportingPlayer->GetNetworkIDString(),
+ teleportingPlayer->GetTeam()->GetName(),
+ where );
+ }
+
+ teleportingPlayer->Teleport( &teleportDestination->GetAbsOrigin(), &teleportDestination->GetAbsAngles(), &vec3_origin );
+
+ // When fighting Merasmus, give players full health on teleport
+ if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) )
+ {
+ if ( teleportingPlayer->IsAlive() )
+ {
+ teleportingPlayer->TakeHealth( teleportingPlayer->GetMaxHealth(), DMG_GENERIC );
+ teleportingPlayer->m_Shared.HealthKitPickupEffects();
+ teleportingPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD );
+ }
+ }
+
+ if ( FStrEq( where, "spawn_loot" ) )
+ {
+ CReliableBroadcastRecipientFilter filter;
+ UTIL_SayText2Filter( filter, teleportingPlayer, false, "#TF_Halloween_Loot_Island", teleportingPlayer->GetPlayerName() );
+ }
+
+ teleportingPlayer->m_Shared.InstantlySniperUnzoom();
+
+ color32 fadeColor = { 255, 255, 255, 100 };
+ UTIL_ScreenFade( teleportingPlayer, fadeColor, 0.25, 0.4, FFADE_IN );
+ }
+}
+
+
+#else
+
+//-----------------------------------------------------------------------------
+
+void CTeleportVortex::ClientThink()
+{
+ // Fade in and out for first and last fraction of time
+ const float flDuration = m_lifeTimer.GetCountdownDuration();
+ const float flRampTime = flDuration / vortex_fade_fraction_denom.GetInt();
+ const float flElapsed = m_lifeTimer.GetElapsedTime();
+
+ float t = 1.0f;
+ if ( ShouldDoBookRampIn() )
+ {
+ t = SCurve( flElapsed / flRampTime );
+ }
+ else if ( ShouldDoBookRampOut() )
+ {
+ t = SCurve( 1.0f - ( flElapsed - flDuration + flRampTime ) / flRampTime );
+ }
+
+// SetRenderColorA( t * 255 );
+
+ m_flScale = t;
+}
+
+//-----------------------------------------------------------------------------
+
+void CTeleportVortex::BuildTransformations( CStudioHdr *pStudioHdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed )
+{
+ // Translate root bone towards the player and oscillate a bit - if in the scale in/out period, translate up directly from the underworld, or down towards it.
+ const Vector vecBob( vortex_book_offset.GetFloat(), 0.0f, vortex_float_amp.GetFloat() * sinf( vortex_float_osc_speed.GetFloat() * gpGlobals->curtime ) );
+ pos[0] = vecBob + Vector( 0.0f, 0.0f, Lerp( m_flScale, -500.0f, 0.0f ) );
+
+ matrix3x4_t mRootBoneRotation;
+
+ // If the local player exists and is alive, render the book so that it's facing him
+ C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
+ if ( pLocalPlayer && pLocalPlayer->IsAlive() )
+ {
+ const Vector vecBook = GetAbsOrigin();
+ const Vector vecPlayerPos = pLocalPlayer->GetAbsOrigin();
+ Vector vecForward = vecPlayerPos - vecBook;
+ if ( VectorNormalize( vecForward ) > 0.1f )
+ {
+ // Calculate a matrix based on the forward direction
+ VectorMatrix( vecForward, mRootBoneRotation );
+ }
+ }
+ else
+ {
+ // Use whatever rotation is in the root bone already
+ QuaternionMatrix( q[0], mRootBoneRotation );
+ }
+
+ // Convert the matrix to a quaternion and slam the root bone rotation
+ MatrixQuaternion( mRootBoneRotation, q[0] );
+
+ // Let the base class actually build the global transforms
+ BaseClass::BuildTransformations( pStudioHdr, pos, q, cameraTransform, boneMask, boneComputed );
+}
+
+//-----------------------------------------------------------------------------
+
+void CTeleportVortex::PlayBookAnimation( const char *pAnimName )
+{
+ int iAnimSequence = LookupSequence( pAnimName );
+ if ( iAnimSequence )
+ {
+ SetSequence( iAnimSequence );
+ SetPlaybackRate( 1.0f );
+ SetCycle( 0 );
+ ResetSequenceInfo();
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void CTeleportVortex::OnDataChanged(DataUpdateType_t updateType)
+{
+ BaseClass::OnDataChanged( updateType );
+
+ if ( m_iState != m_iOldState )
+ {
+ if ( m_iState != VORTEXSTATE_INACTIVE )
+ {
+ // Stop any existing particle effect if necessary
+ AssertMsg( !m_pVortexEffect, "Particle effect should not be active!" );
+
+ // Create the particle effect and play a sound
+ const char *pszEffectName;
+ const char *pszSound;
+ if ( m_iState == VORTEXSTATE_ACTIVE_EYEBALL_MOVED )
+ {
+ pszEffectName = VORTEX_PARTICLE_EFFECT_EYEBALL_MOVED;
+ pszSound = VORTEX_SOUND_EYEBALL_MOVED;
+ }
+ else
+ {
+ pszEffectName = VORTEX_PARTICLE_EFFECT_EYEBALL_DIED;
+ pszSound = VORTEX_SOUND_EYEBALL_DIED;
+
+ PlayBookAnimation( VORTEX_OPEN_OPEN_ANIM );
+ }
+ m_pVortexEffect = ParticleProp()->Create( pszEffectName, PATTACH_ABSORIGIN );
+ EmitSound( pszSound );
+ }
+
+ m_iOldState = m_iState;
+ }
+}
+
+#endif
+
+float CTeleportVortex::GetRampTime()
+{
+ const float flDuration = m_lifeTimer.GetCountdownDuration();
+ return flDuration / vortex_fade_fraction_denom.GetInt();
+}
+
+bool CTeleportVortex::ShouldDoBookRampIn()
+{
+ return m_lifeTimer.GetElapsedTime() <= GetRampTime();
+}
+
+bool CTeleportVortex::ShouldDoBookRampOut()
+{
+ const float flDuration = m_lifeTimer.GetCountdownDuration();
+ return m_lifeTimer.GetElapsedTime() >= flDuration - GetRampTime();
+}
+
+#ifdef CLIENT_DLL
+ #define CHightower_TeleportVortex C_Hightower_TeleportVortex
+#endif
+class CHightower_TeleportVortex : public CTeleportVortex
+{
+ DECLARE_DATADESC();
+ DECLARE_NETWORKCLASS();
+ DECLARE_CLASS( CHightower_TeleportVortex, CTeleportVortex );
+
+public:
+ virtual void Spawn()
+ {
+ BaseClass::Spawn();
+#ifdef GAME_DLL
+ m_nWinningTeam = TF_TEAM_COUNT; // Invalid
+ SetupVortex( false, false );
+ m_lifeTimer.Start( m_flDuration );
+#endif
+ }
+
+ virtual void Touch( CBaseEntity *pOther )
+ {
+#ifdef GAME_DLL
+ const char *pszTarget = CFmtStr( "%s_%s", m_pszDestinationBaseName, ( pOther->GetTeamNumber() == m_nWinningTeam ) ? "winner" : "loser" );
+ SendPlayerToTheUnderworld( ToTFPlayer( pOther ), pszTarget );
+#endif
+ }
+
+#ifdef GAME_DLL
+ void SetAdvantageTeam( inputdata_t &inputdata )
+ {
+ m_nWinningTeam = FStrEq( inputdata.value.String(), "red" ) ? TF_TEAM_RED : TF_TEAM_BLUE;
+ }
+#endif
+private:
+
+#ifdef GAME_DLL
+ int m_nWinningTeam;
+ const char* m_pszDestinationBaseName;
+ float m_flDuration;
+#endif
+};
+
+IMPLEMENT_NETWORKCLASS_ALIASED( Hightower_TeleportVortex, DT_Hightower_TeleportVortex )
+
+BEGIN_NETWORK_TABLE( CHightower_TeleportVortex, DT_Hightower_TeleportVortex )
+ #if defined( CLIENT_DLL )
+ RecvPropInt( RECVINFO( m_iState ) ),
+ #else
+ SendPropInt( SENDINFO( m_iState ), 4, SPROP_UNSIGNED | SPROP_CHANGES_OFTEN ),
+ #endif
+END_NETWORK_TABLE()
+
+LINK_ENTITY_TO_CLASS( hightower_teleport_vortex, CHightower_TeleportVortex );
+
+BEGIN_DATADESC( CHightower_TeleportVortex )
+#ifdef GAME_DLL
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetAdvantageTeam", SetAdvantageTeam ),
+ DEFINE_KEYFIELD( m_flDuration, FIELD_FLOAT, "lifetime" ),
+ DEFINE_KEYFIELD( m_pszDestinationBaseName, FIELD_STRING, "target_base_name" ),
+#endif
+END_DATADESC() \ No newline at end of file