diff options
Diffstat (limited to 'game/server/hl1/hl1_ents.cpp')
| -rw-r--r-- | game/server/hl1/hl1_ents.cpp | 1654 |
1 files changed, 1654 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_ents.cpp b/game/server/hl1/hl1_ents.cpp new file mode 100644 index 0000000..c5ab56e --- /dev/null +++ b/game/server/hl1/hl1_ents.cpp @@ -0,0 +1,1654 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "ai_basenpc.h" +#include "trains.h" +#include "ndebugoverlay.h" +#include "entitylist.h" +#include "engine/IEngineSound.h" +#include "hl1_ents.h" +#include "doors.h" +#include "soundent.h" +#include "hl1_basegrenade.h" +#include "shake.h" +#include "globalstate.h" +#include "soundscape.h" +#include "buttons.h" +#include "Sprite.h" +#include "actanimating.h" +#include "npcevent.h" +#include "func_break.h" +#include "hl1_shareddefs.h" +#include "eventqueue.h" + + +// +// TRIGGERS: trigger_auto, trigger_relay, multimanager: replaced in src by logic_auto and logic_relay +// + +// This trigger will fire when the level spawns (or respawns if not fire once) +// It will check a global state before firing. +#define SF_AUTO_FIREONCE 0x0001 + +class CAutoTrigger : public CBaseEntity +{ + DECLARE_CLASS( CAutoTrigger, CBaseEntity ); +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + DECLARE_DATADESC(); + +private: + + COutputEvent m_OnTrigger; + string_t m_globalstate; +}; +LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ); + +BEGIN_DATADESC( CAutoTrigger ) + DEFINE_KEYFIELD( m_globalstate, FIELD_STRING, "globalstate" ), + + // Outputs + DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"), +END_DATADESC() + + +void CAutoTrigger::Spawn( void ) +{ + Precache(); +} + + +void CAutoTrigger::Precache( void ) +{ + SetNextThink( gpGlobals->curtime + 0.1f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Checks the global state and fires targets if the global state is set. +//----------------------------------------------------------------------------- +void CAutoTrigger::Think( void ) +{ + if ( !m_globalstate || GlobalEntity_GetState( m_globalstate ) == GLOBAL_ON ) + { + m_OnTrigger.FireOutput(NULL, this); + + if ( m_spawnflags & SF_AUTO_FIREONCE ) + UTIL_Remove( this ); + } +} + + +#define SF_RELAY_FIREONCE 0x0001 + +class CTriggerRelay : public CBaseEntity +{ + DECLARE_CLASS( CTriggerRelay, CBaseEntity ); +public: + + CTriggerRelay( void ); + + bool KeyValue( const char *szKeyName, const char *szValue ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void RefireThink( void ); + + int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + DECLARE_DATADESC(); + +private: + USE_TYPE triggerType; + float m_flRefireInterval; + float m_flRefireDuration; + float m_flTimeRefireDone; + + USE_TYPE m_TargetUseType; + float m_flTargetValue; + + COutputEvent m_OnTrigger; +}; +LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ); + +BEGIN_DATADESC( CTriggerRelay ) + DEFINE_FIELD( triggerType, FIELD_INTEGER ), + DEFINE_KEYFIELD( m_flRefireInterval, FIELD_FLOAT, "repeatinterval" ), + DEFINE_KEYFIELD( m_flRefireDuration, FIELD_FLOAT, "repeatduration" ), + DEFINE_FIELD( m_flTimeRefireDone, FIELD_TIME ), + DEFINE_FIELD( m_TargetUseType, FIELD_INTEGER ), + DEFINE_FIELD( m_flTargetValue, FIELD_FLOAT ), + + // Function Pointers + DEFINE_FUNCTION( RefireThink ), + + // Outputs + DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"), +END_DATADESC() + +CTriggerRelay::CTriggerRelay( void ) +{ + m_flRefireInterval = -1; + m_flRefireDuration = -1; + m_flTimeRefireDone = -1; +} + +bool CTriggerRelay::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "triggerstate")) + { + int type = atoi( szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + } + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } + + return true; +} + + +void CTriggerRelay::Spawn( void ) +{ +} + + +void CTriggerRelay::RefireThink( void ) +{ + // sending this as Activator and Caller right now. Seems the safest thing + // since whatever fired the relay the first time may no longer exist. + Use( this, this, m_TargetUseType, m_flTargetValue ); + + if( gpGlobals->curtime > m_flTimeRefireDone ) + { + UTIL_Remove( this ); + } + else + { + SetNextThink( gpGlobals->curtime + m_flRefireInterval ); + } +} + +void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_OnTrigger.FireOutput(pActivator, this); + + if ( m_spawnflags & SF_RELAY_FIREONCE ) + { + UTIL_Remove( this ); + } + + else if( m_flRefireDuration != -1 && m_flTimeRefireDone == -1 ) + { + // Set up to refire this target automatically + m_TargetUseType = useType; + m_flTargetValue = value; + + m_flTimeRefireDone = gpGlobals->curtime + m_flRefireDuration; + SetThink( &CTriggerRelay::RefireThink ); + SetNextThink( gpGlobals->curtime + m_flRefireInterval ); + } +} + + +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. + +class CMultiManager : public CPointEntity +{ + DECLARE_CLASS( CMultiManager, CPointEntity ); +public: + + bool KeyValue( const char *szKeyName, const char *szValue ); + void Spawn ( void ); + void ManagerThink ( void ); + void ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void ManagerReport( void ); +#endif + + bool HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + DECLARE_DATADESC(); + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_flWait; + EHANDLE m_hActivator; + float m_startTime;// Time we started firing + string_t m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire + + COutputEvent m_OnTrigger; + + void InputManagerTrigger( inputdata_t &data ); +}; +LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ); + +// Global Savedata for multi_manager +BEGIN_DATADESC( CMultiManager ) + DEFINE_KEYFIELD( m_flWait, FIELD_FLOAT, "wait" ), + + DEFINE_FIELD( m_cTargets, FIELD_INTEGER ), + DEFINE_FIELD( m_index, FIELD_INTEGER ), + DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( m_startTime, FIELD_TIME ), + DEFINE_ARRAY( m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ), + + // Function Pointers + DEFINE_FUNCTION( ManagerThink ), + DEFINE_FUNCTION( ManagerUse ), +#if _DEBUG + DEFINE_FUNCTION( ManagerReport ), +#endif + + // Outputs + DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"), + DEFINE_INPUTFUNC( FIELD_VOID, "Trigger", InputManagerTrigger ), +END_DATADESC() + +void CMultiManager::InputManagerTrigger( inputdata_t &data ) +{ + ManagerUse ( NULL, NULL, USE_TOGGLE, 0 ); +} + +bool CMultiManager::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( BaseClass::KeyValue( szKeyName, szValue ) ) + { + return true; + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + + UTIL_StripToken( szKeyName, tmp, Q_ARRAYSIZE( tmp ) ); + m_iTargetName [ m_cTargets ] = AllocPooledString( tmp ); + m_flTargetDelay [ m_cTargets ] = atof (szValue); + m_cTargets++; + } + else + { + return false; + } + } + + return true; +} + + +void CMultiManager::Spawn( void ) +{ + SetSolid( SOLID_NONE ); + SetUse ( &CMultiManager::ManagerUse ); + SetThink ( &CMultiManager::ManagerThink); + + // Sort targets + // Quick and dirty bubble sort + int swapped = 1; + + while ( swapped ) + { + swapped = 0; + for ( int i = 1; i < m_cTargets; i++ ) + { + if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] ) + { + // Swap out of order elements + string_t name = m_iTargetName[i]; + float delay = m_flTargetDelay[i]; + m_iTargetName[i] = m_iTargetName[i-1]; + m_flTargetDelay[i] = m_flTargetDelay[i-1]; + m_iTargetName[i-1] = name; + m_flTargetDelay[i-1] = delay; + swapped = 1; + } + } + } +} + + +bool CMultiManager::HasTarget( string_t targetname ) +{ + for ( int i = 0; i < m_cTargets; i++ ) + if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) ) + return true; + + return false; +} + + +// Designers were using this to fire targets that may or may not exist -- +// so I changed it to use the standard target fire code, made it a little simpler. +void CMultiManager::ManagerThink ( void ) +{ + float t; + + t = gpGlobals->curtime - m_startTime; + + while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= t ) + { + FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 ); + m_index++; + } + + if ( m_index >= m_cTargets )// have we fired all targets? + { + SetThink( NULL ); + SetUse ( &CMultiManager::ManagerUse );// allow manager re-use + } + else + { + SetNextThink( m_startTime + m_flTargetDelay[ m_index ] ); + } +} + + +// The USE function builds the time table and starts the entity thinking. +void CMultiManager::ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + m_index = 0; + m_startTime = gpGlobals->curtime; + + m_OnTrigger.FireOutput(pActivator, this); + + // Calculate the time to re-enable the multimanager - just after the last output is fired. + // dvsents2: need to disable multimanager until last output is fired + //m_fEnableTime = gpGlobals->curtime + m_OnTrigger.GetMaxDelay(); + + SetUse( NULL );// disable use until all targets have fired + + SetThink ( &CMultiManager::ManagerThink ); + SetNextThink( gpGlobals->curtime ); +} + + +#if _DEBUG +void CMultiManager::ManagerReport ( void ) +{ + int cIndex; + + for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ ) + { + Msg( "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] ); + } +} +#endif + + +// +// Pendulum +// + +#define SF_PENDULUM_SWING 2 + +LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum ); + +BEGIN_DATADESC( CPendulum ) + DEFINE_FIELD( m_flAccel, FIELD_FLOAT ), + DEFINE_FIELD( m_flTime, FIELD_TIME ), + DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( m_flDampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( m_vCenter, FIELD_VECTOR ), + DEFINE_FIELD( m_vStart, FIELD_VECTOR ), + + DEFINE_KEYFIELD( m_flMoveDistance, FIELD_FLOAT, "pendistance" ), + DEFINE_KEYFIELD( m_flDamp, FIELD_FLOAT, "damp" ), + DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ), + + DEFINE_FUNCTION( PendulumUse ), + DEFINE_FUNCTION( Swing ), + DEFINE_FUNCTION( Stop ), + DEFINE_FUNCTION( RopeTouch ), + + DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), +END_DATADESC() + +void CPendulum::Spawn( void ) +{ + CBaseToggle::AxisDir(); + + m_flDamp *= 0.001; + + if ( FBitSet ( m_spawnflags, SF_DOOR_PASSABLE ) ) + SetSolid( SOLID_NONE ); + else + SetSolid( SOLID_BBOX ); + + SetMoveType( MOVETYPE_PUSH ); + SetModel( STRING(GetModelName()) ); + + if ( m_flMoveDistance != 0 ) + { + if ( m_flSpeed == 0 ) + m_flSpeed = 100; + + m_flAccel = ( m_flSpeed * m_flSpeed ) / ( 2 * fabs( m_flMoveDistance )); // Calculate constant acceleration from speed and distance + m_flMaxSpeed = m_flSpeed; + m_vStart = GetAbsAngles(); + m_vCenter = GetAbsAngles() + ( m_flMoveDistance * 0.05 ) * m_vecMoveAng; + + if ( FBitSet( m_spawnflags, SF_BRUSH_ROTATE_START_ON ) ) + { + SetThink( &CBaseEntity::SUB_CallUseToggle ); + SetNextThink( gpGlobals->curtime + 0.1f ); + } + + m_flSpeed = 0; + SetUse( &CPendulum::PendulumUse ); + + VPhysicsInitShadow( false, false ); + ///VPhysicsGetObject()->SetPosition( GetAbsOrigin(), pev->absangles ); + } + + if ( FBitSet( m_spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch ( &CPendulum::RopeTouch ); + } +} + + +void CPendulum::PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_flSpeed ) // Pendulum is moving, stop it and auto-return if necessary + { + if ( FBitSet( m_spawnflags, SF_BRUSH_ROTATE_START_ON ) ) + { + float delta; + + delta = CBaseToggle::AxisDelta( m_spawnflags, GetAbsAngles(), m_vStart ); + + SetLocalAngularVelocity( m_flMaxSpeed * m_vecMoveAng ); + SetNextThink( gpGlobals->curtime + delta / m_flMaxSpeed); + SetThink( &CPendulum::Stop ); + } + else + { + m_flSpeed = 0; // Dead stop + SetThink( NULL ); + SetLocalAngularVelocity( QAngle( 0, 0, 0 ) ); + } + } + else + { + SetNextThink( gpGlobals->curtime + 0.1f ); // Start the pendulum moving + m_flTime = gpGlobals->curtime; // Save time to calculate dt + SetThink( &CPendulum::Swing ); + m_flDampSpeed = m_flMaxSpeed; + } +} + +void CPendulum::InputActivate( inputdata_t &inputdata ) +{ + SetNextThink( gpGlobals->curtime + 0.1f ); // Start the pendulum moving + m_flTime = gpGlobals->curtime; // Save time to calculate dt + SetThink( &CPendulum::Swing ); + m_flDampSpeed = m_flMaxSpeed; +} + +void CPendulum::Stop( void ) +{ + SetAbsAngles( m_vStart ); + m_flSpeed = 0; + SetThink( NULL ); + SetLocalAngularVelocity( QAngle ( 0, 0, 0 ) ); +} + + +void CPendulum::Blocked( CBaseEntity *pOther ) +{ + m_flTime = gpGlobals->curtime; +} + +void CPendulum::Swing( void ) +{ + float delta, dt; + + delta = CBaseToggle::AxisDelta( m_spawnflags, GetAbsAngles(), m_vCenter ); + dt = gpGlobals->curtime - m_flTime; // How much time has passed? + m_flTime = gpGlobals->curtime; // Remember the last time called + + if ( delta > 0 && m_flAccel > 0 ) + m_flSpeed -= m_flAccel * dt; // Integrate velocity + else + m_flSpeed += m_flAccel * dt; + + if ( m_flSpeed > m_flMaxSpeed ) + m_flSpeed = m_flMaxSpeed; + else if ( m_flSpeed < -m_flMaxSpeed ) + m_flSpeed = -m_flMaxSpeed; + + // scale the destdelta vector by the time spent traveling to get velocity + SetLocalAngularVelocity( m_flSpeed * m_vecMoveAng ); + + // Call this again + SetNextThink( gpGlobals->curtime + 0.1f ); + SetMoveDoneTime( 0.1 ); + + if ( m_flDamp ) + { + m_flDampSpeed -= m_flDamp * m_flDampSpeed * dt; + if ( m_flDampSpeed < 30.0 ) + { + SetAbsAngles( m_vCenter ); + m_flSpeed = 0; + SetThink( NULL ); + SetLocalAngularVelocity( QAngle( 0, 0, 0 ) ); + } + else if ( m_flSpeed > m_flDampSpeed ) + m_flSpeed = m_flDampSpeed; + else if ( m_flSpeed < -m_flDampSpeed ) + m_flSpeed = -m_flDampSpeed; + } +} + +void CPendulum::Touch ( CBaseEntity *pOther ) +{ + if ( m_flBlockDamage <= 0 ) + return; + + // we can't hurt this thing, so we're not concerned with it + if ( !pOther->m_takedamage ) + return; + + // calculate damage based on rotation speed + float damage = m_flBlockDamage * m_flSpeed * 0.01; + + if ( damage < 0 ) + damage = -damage; + + pOther->TakeDamage( CTakeDamageInfo( this, this, damage, DMG_CRUSH ) ); + + Vector vNewVel = (pOther->GetAbsOrigin() - GetAbsOrigin()); + + VectorNormalize( vNewVel ); + + pOther->SetAbsVelocity( vNewVel * damage ); +} + +void CPendulum::RopeTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + {// not a player! + DevMsg ( 2, "Not a client\n" ); + return; + } + + if ( pOther == GetEnemy() ) + return; + + m_hEnemy = pOther; + pOther->SetAbsVelocity( Vector ( 0, 0, 0 ) ); + pOther->SetMoveType( MOVETYPE_NONE ); +} + + +// +// MORTARS +// + +class CFuncMortarField : public CBaseToggle +{ + DECLARE_CLASS( CFuncMortarField, CBaseToggle ); +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + +/* virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[];*/ + + //void EXPORT FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void InputTrigger ( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + + string_t m_iszXController; + string_t m_iszYController; + float m_flSpread; + int m_iCount; + int m_fControl; +}; + +LINK_ENTITY_TO_CLASS( func_mortar_field, CFuncMortarField ); + +BEGIN_DATADESC( CFuncMortarField ) + DEFINE_KEYFIELD( m_iszXController, FIELD_STRING, "m_iszXController" ), + DEFINE_KEYFIELD( m_iszYController, FIELD_STRING, "m_iszYController" ), + DEFINE_KEYFIELD( m_flSpread, FIELD_FLOAT, "m_flSpread" ), + DEFINE_KEYFIELD( m_iCount, FIELD_INTEGER, "m_iCount" ), + DEFINE_KEYFIELD( m_fControl, FIELD_INTEGER, "m_fControl" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Trigger", InputTrigger ), +END_DATADESC() + + +// Drop bombs from above +void CFuncMortarField::Spawn( void ) +{ + SetSolid( SOLID_NONE ); + SetModel( STRING(GetModelName()) ); // set size and link into world + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); +// SetUse( FieldUse ); + Precache(); +} + + +void CFuncMortarField::Precache( void ) +{ + PrecacheModel( "sprites/lgtning.vmt" ); + + PrecacheScriptSound( "MortarField.Trigger" ); +} + +void CFuncMortarField::InputTrigger( inputdata_t &inputdata ) +{ + Vector vecStart; + CollisionProp()->RandomPointInBounds( Vector( 0, 0, 1 ), Vector( 1, 1, 1 ), &vecStart ); + + switch( m_fControl ) + { + case 0: // random + break; + case 1: // Trigger Activator + if (inputdata.pActivator != NULL) + { + vecStart.x = inputdata.pActivator->GetAbsOrigin().x; + vecStart.y = inputdata.pActivator->GetAbsOrigin().y; + } + break; + case 2: // table + { + CBaseEntity *pController; + + if ( m_iszXController != NULL_STRING ) + { + pController = gEntList.FindEntityByName( NULL, STRING(m_iszXController) ); + if (pController != NULL) + { + if ( FClassnameIs( pController, "momentary_rot_button" ) ) + { + CMomentaryRotButton *pXController = static_cast<CMomentaryRotButton*>( pController ); + Vector vecNormalizedPos( pXController->GetPos( pXController->GetLocalAngles() ), 0.0f, 0.0f ); + Vector vecWorldSpace; + CollisionProp()->NormalizedToWorldSpace( vecNormalizedPos, &vecWorldSpace ); + vecStart.x = vecWorldSpace.x; + } + else + { + DevMsg( "func_mortarfield has X controller that isn't a momentary_rot_button.\n" ); + } + } + } + if ( m_iszYController != NULL_STRING ) + { + pController = gEntList.FindEntityByName( NULL, STRING(m_iszYController) ); + if (pController != NULL) + { + if ( FClassnameIs( pController, "momentary_rot_button" ) ) + { + CMomentaryRotButton *pYController = static_cast<CMomentaryRotButton*>( pController ); + Vector vecNormalizedPos( 0.0f, pYController->GetPos( pYController->GetLocalAngles() ), 0.0f ); + Vector vecWorldSpace; + CollisionProp()->NormalizedToWorldSpace( vecNormalizedPos, &vecWorldSpace ); + vecStart.y = vecWorldSpace.y; + } + else + { + DevMsg( "func_mortarfield has Y controller that isn't a momentary_rot_button.\n" ); + } + } + } + } + break; + } + + CPASAttenuationFilter filter( this, ATTN_NONE ); + EmitSound( filter, entindex(), "MortarField.Trigger" ); + + float t = 2.5; + for (int i = 0; i < m_iCount; i++) + { + Vector vecSpot = vecStart; + vecSpot.x += random->RandomFloat( -m_flSpread, m_flSpread ); + vecSpot.y += random->RandomFloat( -m_flSpread, m_flSpread ); + + trace_t tr; + UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -1 ) * MAX_TRACE_LENGTH, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + + CBaseEntity *pMortar = Create( "monster_mortar", tr.endpos, QAngle( 0, 0, 0 ), inputdata.pActivator ); + pMortar->SetNextThink( gpGlobals->curtime + t ); + t += random->RandomFloat( 0.2, 0.5 ); + + if (i == 0) + CSoundEnt::InsertSound ( SOUND_DANGER, tr.endpos, 400, 0.3 ); + } +} + +#ifdef HL1_DLL + +class CMortar : public CHL1BaseGrenade +{ + DECLARE_CLASS( CMortar, CHL1BaseGrenade ); + +public: + void Spawn( void ); + void Precache( void ); + + void MortarExplode( void ); + + int m_spriteTexture; + + DECLARE_DATADESC(); +}; + +BEGIN_DATADESC( CMortar ) + DEFINE_THINKFUNC( MortarExplode ), + //DEFINE_FIELD( m_spriteTexture, FIELD_INTEGER ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( monster_mortar, CMortar ); + +void CMortar::Spawn( ) +{ + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_NONE ); + + SetDamage( 200 ); + SetDamageRadius( GetDamage() * 2.5 ); + + SetThink( &CMortar::MortarExplode ); + SetNextThink( TICK_NEVER_THINK ); + + Precache( ); +} + + +void CMortar::Precache( ) +{ + m_spriteTexture = PrecacheModel( "sprites/lgtning.vmt" ); +} + +void CMortar::MortarExplode( void ) +{ + Vector vecStart = GetAbsOrigin(); + Vector vecEnd = vecStart; + vecEnd.z += 1024; + + UTIL_Beam( vecStart, vecEnd, m_spriteTexture, 0, 0, 0, 0.5, 4.0, 4.0, 100, 0, 255, 160, 100, 128, 0 ); + + trace_t tr; + UTIL_TraceLine( GetAbsOrigin() + Vector( 0, 0, 1024 ), GetAbsOrigin() - Vector( 0, 0, 1024 ), MASK_ALL, this, COLLISION_GROUP_NONE, &tr ); + + + Explode( &tr, DMG_BLAST | DMG_MISSILEDEFENSE ); + UTIL_ScreenShake( tr.endpos, 25.0, 150.0, 1.0, 750, SHAKE_START ); +} + +#endif + +//========================================================= +// Dead HEV suit prop +//========================================================= +class CNPC_DeadHEV : public CAI_BaseNPC +{ + DECLARE_CLASS( CNPC_DeadHEV, CAI_BaseNPC ); +public: + void Spawn( void ); + Class_T Classify ( void ) { return CLASS_NONE; } + float MaxYawSpeed( void ) { return 8.0f; } + + bool KeyValue( const char *szKeyName, const char *szValue ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CNPC_DeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +bool CNPC_DeadHEV::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq( szKeyName, "pose" ) ) + m_iPose = atoi( szValue ); + else + CAI_BaseNPC::KeyValue( szKeyName, szValue ); + + return true; +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CNPC_DeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CNPC_DeadHEV::Spawn( void ) +{ + PrecacheModel("models/player.mdl"); + SetModel( "models/player.mdl" ); + + ClearEffects(); + SetSequence( 0 ); + m_nBody = 1; + m_bloodColor = BLOOD_COLOR_RED; + + SetSequence( LookupSequence( m_szPoses[m_iPose] ) ); + + if ( GetSequence() == -1 ) + { + Msg ( "Dead hevsuit with bad pose\n" ); + SetSequence( 0 ); + ClearEffects(); + AddEffects( EF_BRIGHTLIGHT ); + } + + // Corpses have less health + m_iHealth = 8; + + NPCInitDead(); +} + + +// +// Render parameters trigger +// +// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) +// to its targets when triggered. +// + + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) + +class CRenderFxManager : public CBaseEntity +{ + DECLARE_CLASS( CRenderFxManager, CBaseEntity ); +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // Input handlers. + void InputActivate( inputdata_t &inputdata ); + + DECLARE_DATADESC(); +}; + +BEGIN_DATADESC( CRenderFxManager ) + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ); + + +void CRenderFxManager::Spawn( void ) +{ + AddSolidFlags( FSOLID_NOT_SOLID ); + SetMoveType( MOVETYPE_NONE ); + AddEffects( EF_NODRAW ); +} + + +void CRenderFxManager::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_target != NULL_STRING ) + { + CBaseEntity *pEntity = NULL; + while ( ( pEntity = gEntList.FindEntityByName( pEntity, STRING( m_target ) ) ) != NULL ) + { + if ( !HasSpawnFlags( SF_RENDER_MASKFX ) ) + pEntity->m_nRenderFX = m_nRenderFX; + if ( !HasSpawnFlags( SF_RENDER_MASKAMT ) ) + pEntity->SetRenderColorA( GetRenderColor().a ); + if ( !HasSpawnFlags( SF_RENDER_MASKMODE ) ) + pEntity->m_nRenderMode = m_nRenderMode; + if ( !HasSpawnFlags( SF_RENDER_MASKCOLOR ) ) + pEntity->m_clrRender = m_clrRender; + } + } +} + + +void CRenderFxManager::InputActivate( inputdata_t &inputdata ) +{ + Use( inputdata.pActivator, inputdata.pCaller, USE_ON, 0 ); +} + + +// Link env_sound to soundscape system +LINK_ENTITY_TO_CLASS( env_sound, CEnvSoundscape ); + + +/////////////////////// +//XEN! +////////////////////// + + +#define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr" +#define XEN_PLANT_HIDE_TIME 5 + +class CXenPLight : public CActAnimating +{ + DECLARE_CLASS( CXenPLight, CActAnimating ); + +public: + + DECLARE_DATADESC(); + + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + + void LightOn( void ); + void LightOff( void ); + + float m_flDmgTime; + + +private: + CSprite *m_pGlow; +}; + +LINK_ENTITY_TO_CLASS( xen_plantlight, CXenPLight ); + +BEGIN_DATADESC( CXenPLight ) + DEFINE_FIELD( m_pGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( m_flDmgTime, FIELD_FLOAT ), +END_DATADESC() + +void CXenPLight::Spawn( void ) +{ + Precache(); + + SetModel( "models/light.mdl" ); + + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); + + UTIL_SetSize( this, Vector(-80,-80,0), Vector(80,80,32)); + SetActivity( ACT_IDLE ); + SetNextThink( gpGlobals->curtime + 0.1 ); + SetCycle( random->RandomFloat(0,1) ); + + m_pGlow = CSprite::SpriteCreate( XEN_PLANT_GLOW_SPRITE, GetLocalOrigin() + Vector(0,0,(WorldAlignMins().z+WorldAlignMaxs().z)*0.5), FALSE ); + m_pGlow->SetTransparency( kRenderGlow, GetRenderColor().r, GetRenderColor().g, GetRenderColor().b, GetRenderColor().a, m_nRenderFX ); + m_pGlow->SetAttachment( this, 1 ); +} + + +void CXenPLight::Precache( void ) +{ + PrecacheModel( "models/light.mdl" ); + PrecacheModel( XEN_PLANT_GLOW_SPRITE ); +} + + +void CXenPLight::Think( void ) +{ + StudioFrameAdvance(); + SetNextThink( gpGlobals->curtime + 0.1 ); + + switch( GetActivity() ) + { + case ACT_CROUCH: + if ( IsSequenceFinished() ) + { + SetActivity( ACT_CROUCHIDLE ); + LightOff(); + } + break; + + case ACT_CROUCHIDLE: + if ( gpGlobals->curtime > m_flDmgTime ) + { + SetActivity( ACT_STAND ); + LightOn(); + } + break; + + case ACT_STAND: + if ( IsSequenceFinished() ) + SetActivity( ACT_IDLE ); + break; + + case ACT_IDLE: + default: + break; + } +} + + +void CXenPLight::Touch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() ) + { + m_flDmgTime = gpGlobals->curtime + XEN_PLANT_HIDE_TIME; + if ( GetActivity() == ACT_IDLE || GetActivity() == ACT_STAND ) + { + SetActivity( ACT_CROUCH ); + } + } +} + + +void CXenPLight::LightOn( void ) +{ + variant_t Value; + g_EventQueue.AddEvent( STRING( m_target ), "TurnOn", Value, 0, this, this ); + + if ( m_pGlow ) + m_pGlow->RemoveEffects( EF_NODRAW ); +} + + +void CXenPLight::LightOff( void ) +{ + variant_t Value; + g_EventQueue.AddEvent( STRING( m_target ), "TurnOff", Value, 0, this, this ); + + if ( m_pGlow ) + m_pGlow->AddEffects( EF_NODRAW ); +} + + +class CXenHair : public CActAnimating +{ + DECLARE_CLASS( CXenHair, CActAnimating ); +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( xen_hair, CXenHair ); + +#define SF_HAIR_SYNC 0x0001 + +void CXenHair::Spawn( void ) +{ + Precache(); + SetModel( "models/hair.mdl" ); + UTIL_SetSize( this, Vector(-4,-4,0), Vector(4,4,32)); + SetSequence( 0 ); + + if ( !HasSpawnFlags( SF_HAIR_SYNC ) ) + { + SetCycle( random->RandomFloat( 0,1) ); + m_flPlaybackRate = random->RandomFloat( 0.7, 1.4 ); + } + ResetSequenceInfo( ); + + SetSolid( SOLID_NONE ); + SetMoveType( MOVETYPE_NONE ); + SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.4 ) ); // Load balance these a bit +} + + +void CXenHair::Think( void ) +{ + StudioFrameAdvance(); + SetNextThink( gpGlobals->curtime + 0.1 ); +} + + +void CXenHair::Precache( void ) +{ + PrecacheModel( "models/hair.mdl" ); +} + +class CXenTreeTrigger : public CBaseEntity +{ + DECLARE_CLASS( CXenTreeTrigger, CBaseEntity ); +public: + void Touch( CBaseEntity *pOther ); + static CXenTreeTrigger *TriggerCreate( CBaseEntity *pOwner, const Vector &position ); +}; +LINK_ENTITY_TO_CLASS( xen_ttrigger, CXenTreeTrigger ); + +CXenTreeTrigger *CXenTreeTrigger::TriggerCreate( CBaseEntity *pOwner, const Vector &position ) +{ + CXenTreeTrigger *pTrigger = CREATE_ENTITY( CXenTreeTrigger, "xen_ttrigger" ); + pTrigger->SetAbsOrigin( position ); + + pTrigger->SetSolid( SOLID_BBOX ); + pTrigger->AddSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID ); + pTrigger->SetMoveType( MOVETYPE_NONE ); + pTrigger->SetOwnerEntity( pOwner ); + + return pTrigger; +} + + +void CXenTreeTrigger::Touch( CBaseEntity *pOther ) +{ + if ( GetOwnerEntity() ) + { + GetOwnerEntity()->Touch( pOther ); + } +} + +#define TREE_AE_ATTACK 1 + +class CXenTree : public CActAnimating +{ + DECLARE_CLASS( CXenTree, CActAnimating ); +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int OnTakeDamage( const CTakeDamageInfo &info ) { Attack(); return 0; } + void HandleAnimEvent( animevent_t *pEvent ); + void Attack( void ); + Class_T Classify( void ) { return CLASS_ALIEN_PREDATOR; } + + DECLARE_DATADESC(); + +private: + CXenTreeTrigger *m_pTrigger; +}; + +LINK_ENTITY_TO_CLASS( xen_tree, CXenTree ); + +BEGIN_DATADESC( CXenTree ) + DEFINE_FIELD( m_pTrigger, FIELD_CLASSPTR ), +END_DATADESC() + +void CXenTree::Spawn( void ) +{ + Precache(); + + SetModel( "models/tree.mdl" ); + SetMoveType( MOVETYPE_NONE ); + SetSolid ( SOLID_BBOX ); + + m_takedamage = DAMAGE_YES; + + UTIL_SetSize( this, Vector(-30,-30,0), Vector(30,30,188)); + SetActivity( ACT_IDLE ); + SetNextThink( gpGlobals->curtime + 0.1 ); + SetCycle( random->RandomFloat( 0,1 ) ); + m_flPlaybackRate = random->RandomFloat( 0.7, 1.4 ); + + Vector triggerPosition, vForward; + + AngleVectors( GetAbsAngles(), &vForward ); + triggerPosition = GetAbsOrigin() + (vForward * 64); + + // Create the trigger + m_pTrigger = CXenTreeTrigger::TriggerCreate( this, triggerPosition ); + UTIL_SetSize( m_pTrigger, Vector( -24, -24, 0 ), Vector( 24, 24, 128 ) ); +} + +void CXenTree::Precache( void ) +{ + PrecacheModel( "models/tree.mdl" ); + PrecacheModel( XEN_PLANT_GLOW_SPRITE ); + + PrecacheScriptSound( "XenTree.AttackMiss" ); + PrecacheScriptSound( "XenTree.AttackHit" ); +} + + +void CXenTree::Touch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() && FClassnameIs( pOther, "monster_bigmomma" ) ) + return; + + Attack(); +} + + +void CXenTree::Attack( void ) +{ + if ( GetActivity() == ACT_IDLE ) + { + SetActivity( ACT_MELEE_ATTACK1 ); + m_flPlaybackRate = random->RandomFloat( 1.0, 1.4 ); + + CPASAttenuationFilter filter( this ); + EmitSound( filter, entindex(), "XenTree.AttackMiss" ); + } +} + + +void CXenTree::HandleAnimEvent( animevent_t *pEvent ) +{ + switch( pEvent->event ) + { + case TREE_AE_ATTACK: + { + CBaseEntity *pList[8]; + BOOL sound = FALSE; + int count = UTIL_EntitiesInBox( pList, 8, m_pTrigger->GetAbsOrigin() + m_pTrigger->WorldAlignMins(), m_pTrigger->GetAbsOrigin() + m_pTrigger->WorldAlignMaxs(), FL_NPC|FL_CLIENT ); + + Vector forward; + AngleVectors( GetAbsAngles(), &forward ); + + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->GetOwnerEntity() != this ) + { + sound = true; + pList[i]->TakeDamage( CTakeDamageInfo(this, this, 25, DMG_CRUSH | DMG_SLASH ) ); + pList[i]->ViewPunch( QAngle( 15, 0, 18 ) ); + + pList[i]->SetAbsVelocity( pList[i]->GetAbsVelocity() + forward * 100 ); + } + } + } + + if ( sound ) + { + CPASAttenuationFilter filter( this ); + EmitSound( filter, entindex(), "XenTree.AttackHit" ); + } + } + return; + } + + BaseClass::HandleAnimEvent( pEvent ); +} + +void CXenTree::Think( void ) +{ + StudioFrameAdvance(); + SetNextThink( gpGlobals->curtime + 0.1 ); + DispatchAnimEvents( this ); + + switch( GetActivity() ) + { + case ACT_MELEE_ATTACK1: + if ( IsSequenceFinished() ) + { + SetActivity( ACT_IDLE ); + m_flPlaybackRate = random->RandomFloat( 0.6f, 1.4f ); + } + break; + + default: + case ACT_IDLE: + break; + + } +} + +class CXenSpore : public CActAnimating +{ + DECLARE_CLASS( CXenSpore, CActAnimating ); +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); +// void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ) {} + + static const char *pModelNames[]; +}; + +class CXenSporeSmall : public CXenSpore +{ + DECLARE_CLASS( CXenSporeSmall, CXenSpore ); + void Spawn( void ); +}; + +class CXenSporeMed : public CXenSpore +{ + DECLARE_CLASS( CXenSporeMed, CXenSpore ); + void Spawn( void ); +}; + +class CXenSporeLarge : public CXenSpore +{ + DECLARE_CLASS( CXenSporeLarge, CXenSpore ); + void Spawn( void ); + + static const Vector m_hullSizes[]; +}; + +// Fake collision box for big spores +class CXenHull : public CPointEntity +{ + DECLARE_CLASS( CXenHull, CPointEntity ); +public: + static CXenHull *CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ); + Class_T Classify( void ) { return CLASS_ALIEN_PREDATOR; } +}; + +CXenHull *CXenHull::CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ) +{ + CXenHull *pHull = CREATE_ENTITY( CXenHull, "xen_hull" ); + + UTIL_SetOrigin( pHull, source->GetAbsOrigin() + offset ); + pHull->SetSolid( SOLID_BBOX ); + pHull->SetMoveType( MOVETYPE_NONE ); + pHull->SetOwnerEntity( source ); + UTIL_SetSize( pHull, mins, maxs ); + pHull->SetRenderColorA( 0 ); + pHull->m_nRenderMode = kRenderTransTexture; + return pHull; +} + + +LINK_ENTITY_TO_CLASS( xen_spore_small, CXenSporeSmall ); +LINK_ENTITY_TO_CLASS( xen_spore_medium, CXenSporeMed ); +LINK_ENTITY_TO_CLASS( xen_spore_large, CXenSporeLarge ); +LINK_ENTITY_TO_CLASS( xen_hull, CXenHull ); + +void CXenSporeSmall::Spawn( void ) +{ + m_nSkin = 0; + CXenSpore::Spawn(); + UTIL_SetSize( this, Vector(-16,-16,0), Vector(16,16,64)); +} +void CXenSporeMed::Spawn( void ) +{ + m_nSkin = 1; + CXenSpore::Spawn(); + UTIL_SetSize( this, Vector(-40,-40,0), Vector(40,40,120)); +} + + +// I just eyeballed these -- fill in hulls for the legs +const Vector CXenSporeLarge::m_hullSizes[] = +{ + Vector( 90, -25, 0 ), + Vector( 25, 75, 0 ), + Vector( -15, -100, 0 ), + Vector( -90, -35, 0 ), + Vector( -90, 60, 0 ), +}; + +void CXenSporeLarge::Spawn( void ) +{ + m_nSkin = 2; + CXenSpore::Spawn(); + UTIL_SetSize( this, Vector(-48,-48,110), Vector(48,48,240)); + + Vector forward, right; + + AngleVectors( GetAbsAngles(), &forward, &right, NULL ); + + // Rotate the leg hulls into position + for ( int i = 0; i < ARRAYSIZE(m_hullSizes); i++ ) + { + CXenHull::CreateHull( this, Vector(-12, -12, 0 ), Vector( 12, 12, 120 ), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right) ); + } +} + +void CXenSpore::Spawn( void ) +{ + Precache(); + + SetModel( pModelNames[m_nSkin] ); + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_BBOX ); + m_takedamage = DAMAGE_NO; + +// SetActivity( ACT_IDLE ); + SetSequence( 0 ); + SetCycle( random->RandomFloat( 0.0f, 1.0f ) ); + m_flPlaybackRate = random->RandomFloat( 0.7f, 1.4f ); + ResetSequenceInfo( ); + SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.4f ) ); // Load balance these a bit +} + +const char *CXenSpore::pModelNames[] = +{ + "models/fungus(small).mdl", + "models/fungus.mdl", + "models/fungus(large).mdl", +}; + + +void CXenSpore::Precache( void ) +{ + PrecacheModel( (char *)pModelNames[m_nSkin] ); +} + + +void CXenSpore::Touch( CBaseEntity *pOther ) +{ +} + + + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CHL1Gib::WaitTillLand ( void ) +{ + if ( !IsInWorld() ) + { + UTIL_Remove( this ); + return; + } + + if ( GetAbsVelocity() == vec3_origin ) + { + /* SetRenderColorA( 255 ); + m_nRenderMode = kRenderTransTexture; + AddSolidFlags( FSOLID_NOT_SOLID );*/ + + SetNextThink( gpGlobals->curtime + m_lifeTime ); + SetThink ( &CBaseEntity::SUB_FadeOut ); + + // If you bleed, you stink! + /* if ( m_bloodColor != DONT_BLEED ) + { + // ok, start stinkin! + CSoundEnt::InsertSound ( bits_SOUND_MEAT, pev->origin, 384, 25 ); + }*/ + } + else + { + // wait and check again in another half second. + SetNextThink( gpGlobals->curtime + 0.5 ); + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CHL1Gib::BounceGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + trace_t tr; + + if ( GetFlags() & FL_ONGROUND) + { + SetAbsVelocity( GetAbsVelocity() * 0.9 ); + + SetAbsAngles( QAngle( 0, GetAbsAngles().y, 0 ) ); + SetLocalAngularVelocity( QAngle( 0, GetLocalAngularVelocity().y, 0 ) ); + } + else + { + if ( m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + + if ( m_material != matNone && random->RandomInt( 0, 2 ) == 0 ) + { + float volume; + float zvel = fabs( GetAbsVelocity().z ); + + volume = 0.8 * MIN(1.0, ((float)zvel) / 450.0); + + CBreakable::MaterialSoundRandom( entindex(), (Materials)m_material, volume ); + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CHL1Gib::StickyGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + trace_t tr; + + SetThink ( &CHL1Gib::SUB_Remove ); + SetNextThink( gpGlobals->curtime + 10 ); + + if ( !FClassnameIs( pOther, "worldspawn" ) ) + { + SetNextThink( gpGlobals->curtime ); + return; + } + + UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 32, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + SetAbsVelocity( tr.plane.normal * -1 ); + + QAngle qAngle; + + VectorAngles( GetAbsVelocity(), qAngle ); + SetAbsAngles( qAngle ); + + SetAbsVelocity( vec3_origin ); + SetLocalAngularVelocity( QAngle( 0, 0, 0 ) ); + SetMoveType( MOVETYPE_NONE ); +} + +// +// Throw a chunk +// +void CHL1Gib::Spawn( const char *szGibModel ) +{ + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + + SetFriction( 0.55 ); // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + SetRenderColorA( 255 ); + m_nRenderMode = kRenderNormal; + m_nRenderFX = kRenderFxNone; + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetClassname( "gib" ); + + SetModel( szGibModel ); + UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0)); + + SetNextThink( gpGlobals->curtime + 4 ); + + m_lifeTime = 25; + + SetThink ( &CHL1Gib::WaitTillLand ); + SetTouch ( &CHL1Gib::BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +LINK_ENTITY_TO_CLASS( hl1gib, CHL1Gib ); + +BEGIN_DATADESC( CHL1Gib ) + // Function Pointers + DEFINE_FUNCTION( BounceGibTouch ), + DEFINE_FUNCTION( StickyGibTouch ), + DEFINE_FUNCTION( WaitTillLand ), + + DEFINE_FIELD( m_bloodColor, FIELD_INTEGER ), + DEFINE_FIELD( m_cBloodDecals, FIELD_INTEGER ), + DEFINE_FIELD( m_material, FIELD_INTEGER ), + DEFINE_FIELD( m_lifeTime, FIELD_FLOAT ), +END_DATADESC() + +#define SF_ENDSECTION_USEONLY 0x0001 + +class CTriggerEndSection : public CBaseEntity +{ + DECLARE_CLASS( CTriggerEndSection, CBaseEntity ); + +public: + void Spawn( void ); + void InputEndSection( inputdata_t &data ); + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ); + +BEGIN_DATADESC( CTriggerEndSection ) + DEFINE_INPUTFUNC( FIELD_VOID, "EndSection", InputEndSection ), +END_DATADESC() + +void CTriggerEndSection::Spawn( void ) +{ + if ( gpGlobals->deathmatch ) + { + UTIL_Remove( this ); + return; + } +} + +void CTriggerEndSection::InputEndSection( inputdata_t &data ) +{ + CBaseEntity *pPlayer = UTIL_GetLocalPlayer(); + + if ( pPlayer ) + { + //HACKY MCHACK - This works, but it's nasty. Alfred is going to fix a + //bug in gameui that prevents you from dropping to the main menu after + // calling disconnect. + engine->ClientCommand ( pPlayer->edict(), "toggleconsole;disconnect\n"); + } + + UTIL_Remove( this ); +} |