source-engine/game/server/tf2/tf_obj_tunnel.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

572 lines
16 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "tf_obj.h"
#include "tf_obj_mapdefined.h"
#include "engine/IEngineSound.h"
#include "entityoutput.h"
#include "tf_shareddefs.h"
#include "triggers.h"
#include "shake.h"
#include "tf_player.h"
#include "tf_gamerules.h"
#define TUNNEL_THINK_INTERVAL 0.1f
#define TUNNEL_FADE_TIME 1.0f
#define MAX_TUNNEL_DURATION 30.0f
// If tunneling takes longer than this, use a countdown
#define TUNNEL_DURATION_MESSAGE_NEEDED 3.0f
// It takes this long to tunnel
static ConVar tf_tunnel_time( "tf_tunnel_time", "2", 0, "Takes this long to traverse a tunnel." );
class CObjectTunnel : public CObjectMapDefined
{
DECLARE_CLASS( CObjectTunnel, CObjectMapDefined );
public:
DECLARE_SERVERCLASS();
virtual void Spawn( void );
virtual void Killed( void );
int UpdateTransmitState();
private:
};
IMPLEMENT_SERVERCLASS_ST(CObjectTunnel, DT_ObjectTunnel)
END_SEND_TABLE();
int CObjectTunnel::UpdateTransmitState()
{
return SetTransmitState( FL_EDICT_ALWAYS );
}
void CObjectTunnel::Spawn( void )
{
BaseClass::Spawn();
AddFlag( FL_NOTARGET );
SetType( OBJ_TUNNEL );
}
LINK_ENTITY_TO_CLASS(obj_tunnel,CObjectTunnel);
LINK_ENTITY_TO_CLASS(obj_tunnel_prop,CObjectTunnel);
//-----------------------------------------------------------------------------
// Purpose: Object has been blown up. Tunnels are never fully destroyed, so they stay on the minimap.
//-----------------------------------------------------------------------------
void CObjectTunnel::Killed( void )
{
m_bDying = true;
RemoveAllSappers( this );
// Do an explosion.
CPASFilter filter( GetAbsOrigin() );
te->Explosion(
filter,
0.0,
&GetAbsOrigin(),
g_sModelIndexFireball,
5.4, // radius
15,
TE_EXPLFLAG_NODLIGHTS,
256,
200);
// Become non-solid and invisible
VPhysicsDestroyObject();
AddSolidFlags( FSOLID_NOT_SOLID );
m_takedamage = DAMAGE_NO;
AddEffects( EF_NODRAW );
}
class CInfoTunnelExit : public CPointEntity
{
public:
DECLARE_CLASS( CInfoTunnelExit, CPointEntity );
private:
};
LINK_ENTITY_TO_CLASS(info_tunnel_exit,CInfoTunnelExit);
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CObjectTunnelTrigger : public CBaseTrigger
{
DECLARE_CLASS( CObjectTunnelTrigger, CBaseTrigger );
public:
CObjectTunnelTrigger();
DECLARE_DATADESC();
virtual void Precache();
virtual void Spawn();
virtual void Activate();
virtual void StartTouch( CBaseEntity *pOther );
void SetActive( bool active );
bool GetActive( void ) const;
void InputSetActive( inputdata_t &inputdata );
void InputSetInactive( inputdata_t &inputdata );
void InputToggleActive( inputdata_t &inputdata );
void InputSetTarget( inputdata_t &inputdata );
void InputSetTeleportDuration( inputdata_t &inputdata );
void InputSetTeleportVelocity( inputdata_t &inputdata );
virtual void TunnelThink();
private:
float GetTeleportDuration( void );
bool m_bActive;
CHandle< CInfoTunnelExit > m_hTunnelExit;
COutputEvent OnTunnelTriggerStart;
COutputEvent OnTunnelTriggerEnd;
struct TunnelPlayer
{
CHandle< CBaseTFPlayer > player;
Vector startpos;
float tunnelstarted;
float duration;
float teleporttime;
float fadeintime;
bool exitstarted;
float fadetime;
int iremaining;
int ilastremaining;
bool needremainigcounter;
};
CUtlVector< TunnelPlayer > m_Tunneling;
void StartTunneling( CBaseTFPlayer *player );
bool KeepTunneling( TunnelPlayer *tunnel );
float m_flTeleportDuration;
float m_flTeleportVelocity;
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CObjectTunnelTrigger::CObjectTunnelTrigger()
{
m_flTeleportDuration = -1.0f;
m_flTeleportVelocity = 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *player -
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::StartTunneling( CBaseTFPlayer *player )
{
if ( !player )
return;
// Ignore if it's already in the list
int c = m_Tunneling.Count();
for ( int i = 0 ; i < c; i++ )
{
TunnelPlayer *tp = &m_Tunneling[ i ];
if ( tp->player == player )
{
return;
}
}
TunnelPlayer tunnel;
tunnel.player = player;
tunnel.tunnelstarted = gpGlobals->curtime;
tunnel.duration = GetTeleportDuration();
tunnel.teleporttime = tunnel.tunnelstarted + tunnel.duration;
tunnel.exitstarted = false;
tunnel.startpos = player->GetAbsOrigin();
tunnel.iremaining = (int)tunnel.duration;
tunnel.ilastremaining = tunnel.iremaining;
tunnel.needremainigcounter = ( tunnel.iremaining > TUNNEL_DURATION_MESSAGE_NEEDED ) ? true : false;
// Fade user screen to black
color32 black = {0,0,0,255};
float duration = tunnel.duration;
float fadeouttime = TUNNEL_FADE_TIME;
float holdtime = 0.0f;
if ( duration < 2 * TUNNEL_FADE_TIME )
{
fadeouttime = duration * 0.5f;
}
else
{
fadeouttime = TUNNEL_FADE_TIME;
holdtime = duration - 2 * fadeouttime;
}
tunnel.fadetime = fadeouttime;
tunnel.fadeintime = tunnel.tunnelstarted + fadeouttime + holdtime;
UTIL_ScreenFade( player, black, fadeouttime, holdtime, FFADE_OUT | FFADE_STAYOUT | FFADE_PURGE );
m_Tunneling.AddToTail( tunnel );
player->SetMoveType( MOVETYPE_NONE );
player->EnableControl( false );
player->AddEffects( EF_NODRAW );
player->AddSolidFlags( FSOLID_NOT_SOLID );
CPASAttenuationFilter filter( player, "ObjectTunnelTrigger.TeleportSound" );
EmitSound( filter, player->entindex(), "ObjectTunnelTrigger.TeleportSound" );
OnTunnelTriggerStart.FireOutput( player, this );
}
float CObjectTunnelTrigger::GetTeleportDuration( void )
{
float duration = m_flTeleportDuration;
if ( m_flTeleportVelocity > 0.0f && m_hTunnelExit != NULL )
{
Vector delta = m_hTunnelExit->GetAbsOrigin() - GetAbsOrigin();
float dist = delta.Length();
duration = dist / m_flTeleportVelocity;
}
else if ( m_flTeleportDuration == -1.0f )
{
Msg( "obj_tunnel_trigger: must set TeleportVelocity or TeleportDuration" );
m_flTeleportDuration = tf_tunnel_time.GetFloat();
}
duration = MIN( duration, MAX_TUNNEL_DURATION );
return duration;
}
bool CObjectTunnelTrigger::KeepTunneling( TunnelPlayer *tunnel )
{
if ( !tunnel || ( tunnel->player == NULL ) )
{
return false;
}
float remaining = tunnel->teleporttime - gpGlobals->curtime + 0.5f;
remaining = MAX( 0.0f, remaining );
tunnel->iremaining = (int)( remaining );
if ( !tunnel->exitstarted )
{
if ( gpGlobals->curtime > tunnel->fadeintime )
{
tunnel->exitstarted = true;
// Fade user screen to black
color32 black = {0,0,0,255};
UTIL_ScreenFade( tunnel->player, black, tunnel->fadetime, 0.0, FFADE_IN | FFADE_PURGE );
// Move to tunnel exit spot now that we're half-way through teleport
if ( m_hTunnelExit != NULL )
{
tunnel->player->EnableControl( true );
tunnel->player->RemoveEffects( EF_NODRAW );
// Change the player to non-solid before the teleport, so the physics system doesn't think he
// actually moved this distance:
int OriginalSolidFlags = tunnel->player->GetSolidFlags();
tunnel->player->AddSolidFlags( FSOLID_NOT_SOLID);
// Do a placement test to prevent the player from teleporting inside another player, the ground, or just to help
// prevent badly placed tunnels from causing stuck situations.
Vector vTarget = m_hTunnelExit->GetAbsOrigin();
Vector vOriginal = vTarget;
if ( !EntityPlacementTest( tunnel->player, vOriginal, vTarget, true ) )
{
Warning("Couldn't place entity after tunnel teleport.\n");
}
tunnel->player->Teleport( &vTarget /*m_hTunnelExit->GetAbsOrigin()*/, &m_hTunnelExit->GetAbsAngles(), NULL );
tunnel->player->SnapEyeAngles( m_hTunnelExit->GetAbsAngles() );
tunnel->player->SetAbsVelocity( vec3_origin );
// Restore the player's solid flags.
tunnel->player->SetSolidFlags(OriginalSolidFlags);
}
}
// Can't quite do this because the player's weapons are still visible flying across the map even if
// he is hidden
#if 0
else if ( gpGlobals->curtime > tunnel->tunnelstarted + tunnel->fadetime )
{
float travel_time = tunnel->duration - 2 * tunnel->fadetime;
if ( travel_time > 0.0f )
{
float f = ( gpGlobals->curtime - tunnel->tunnelstarted - tunnel->fadetime ) / travel_time;
f = clamp( f, 0.0f, 1.0f );
if ( m_hTunnelExit != NULL )
{
Vector delta = m_hTunnelExit->GetAbsOrigin() - tunnel->startpos;
Vector currentPos;
VectorMA( tunnel->startpos, f, delta, currentPos );
tunnel->player->Teleport( &currentPos, NULL, NULL );
}
}
}
#endif
}
if ( tunnel->ilastremaining != tunnel->iremaining &&
tunnel->needremainigcounter &&
tunnel->iremaining >= 1 &&
tunnel->player != NULL )
{
// Counter
ClientPrint( tunnel->player, HUD_PRINTCENTER, UTIL_VarArgs("\nExiting tunnel in %d %s\n", tunnel->iremaining, tunnel->iremaining > 1 ? "seconds" : "second" ) );
}
tunnel->ilastremaining = tunnel->iremaining;
// TODO: Play footstep or some other teleport sounds occasionaly to this player?
bool done = ( gpGlobals->curtime > tunnel->teleporttime ) ? true : false;
if ( done )
{
color32 black = {0,0,0,255};
UTIL_ScreenFade( tunnel->player, black, 0.0f, 0.0f, FFADE_IN | FFADE_PURGE );
tunnel->player->SetMoveType( MOVETYPE_WALK );
tunnel->player->EnableControl( true );
tunnel->player->RemoveEffects( EF_NODRAW );
tunnel->player->RemoveSolidFlags( FSOLID_NOT_SOLID );
// TODO: Play an exit sound??
OnTunnelTriggerEnd.FireOutput( tunnel->player, this );
}
return !done;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::TunnelThink()
{
// Make sure it's not already in the list
int c = m_Tunneling.Count();
for ( int i = c - 1; i >= 0; i-- )
{
TunnelPlayer *tp = &m_Tunneling[ i ];
if ( !KeepTunneling( tp ) )
{
m_Tunneling.Remove( i );
}
}
SetNextThink( gpGlobals->curtime + TUNNEL_THINK_INTERVAL );
}
void CObjectTunnelTrigger::Precache()
{
BaseClass::Precache();
PrecacheScriptSound( "ObjectTunnelTrigger.TeleportSound" );
PrecacheScriptSound( "ObjectTunnelTrigger.DisabledSound" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::Spawn()
{
Precache();
SetSolid( SOLID_BSP );
AddSolidFlags( FSOLID_TRIGGER );
SetMoveType( MOVETYPE_NONE );
AddEffects( EF_NODRAW );
SetModel( STRING( GetModelName() ) );
AddFlag( FL_NOTARGET );
m_bActive = false;
}
//-----------------------------------------------------------------------------
// Purpose: See if we've got a gather point specified
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::Activate( void )
{
BaseClass::Activate();
if (m_target != NULL_STRING)
{
CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, m_target );
if ( pEnt && FClassnameIs( pEnt, "info_tunnel_exit" ) )
{
m_hTunnelExit = static_cast< CInfoTunnelExit * >( pEnt );
}
else
{
Msg( "CObjectTunnelTrigger::Activate, unable to connect tunnel to target %s\n",
STRING( m_target ) );
}
}
else
{
Msg( "CObjectTunnelTrigger::Activate, missing target\n" );
}
SetActive( true );
SetThink( TunnelThink );
SetNextThink( gpGlobals->curtime + TUNNEL_THINK_INTERVAL );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : active -
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::SetActive( bool active )
{
m_bActive = active;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CObjectTunnelTrigger::GetActive( void ) const
{
return m_bActive;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::InputSetActive( inputdata_t &inputdata )
{
SetActive( true );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::InputSetInactive( inputdata_t &inputdata )
{
SetActive( false );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::InputToggleActive( inputdata_t &inputdata )
{
if ( m_bActive )
{
SetActive( false );
}
else
{
SetActive( true );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::InputSetTarget( inputdata_t &inputdata )
{
CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, inputdata.value.String() );
if ( pEnt && FClassnameIs( pEnt, "info_tunnel_exit" ) )
{
m_hTunnelExit = static_cast< CInfoTunnelExit * >( pEnt );
}
else
{
Msg( "CObjectTunnelTrigger::InputSetTarget: Couldn't find info_tunnel_exit named %s\n",
inputdata.value.String() );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::InputSetTeleportDuration( inputdata_t &inputdata )
{
m_flTeleportDuration = inputdata.value.Float();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::InputSetTeleportVelocity( inputdata_t &inputdata )
{
m_flTeleportVelocity = inputdata.value.Float();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOther -
//-----------------------------------------------------------------------------
void CObjectTunnelTrigger::StartTouch( CBaseEntity *pOther )
{
if ( !pOther || !pOther->IsPlayer() )
return;
// Only works for my team, of course
if ( !pOther->InSameTeam( this ) )
return;
if ( m_hTunnelExit == NULL )
return;
// It's been damaged to the point of being disabled
if ( !GetActive() )
{
// Play a deny sound
CPASAttenuationFilter filter( pOther, "ObjectTunnelTrigger.DisabledSound" );
EmitSound( filter, pOther->entindex(), "ObjectTunnelTrigger.DisabledSound" );
return;
}
StartTunneling( (CBaseTFPlayer *)pOther );
}
LINK_ENTITY_TO_CLASS(obj_tunnel_trigger,CObjectTunnelTrigger);
BEGIN_DATADESC( CObjectTunnelTrigger )
// inputs
DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ),
DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ),
DEFINE_INPUTFUNC( FIELD_VOID, "ToggleActive", InputToggleActive ),
DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTeleportDuration", InputSetTeleportDuration ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTeleportVelocity", InputSetTeleportVelocity ),
// outputs
DEFINE_OUTPUT( OnTunnelTriggerStart, "OnTunnelTriggerStart" ),
DEFINE_OUTPUT( OnTunnelTriggerEnd, "OnTunnelTriggerEnd" ),
// keyvalues
DEFINE_KEYFIELD_NOT_SAVED( m_flTeleportDuration, FIELD_FLOAT, "TeleportDuration" ),
DEFINE_KEYFIELD_NOT_SAVED( m_flTeleportVelocity, FIELD_FLOAT, "TeleportVelocity" ),
END_DATADESC()