You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
571 lines
16 KiB
571 lines
16 KiB
//========= 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( ¤tPos, 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()
|
|
|