/*** * * Copyright (c) 2001, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * Valve LLC. All other use, distribution, or modification is prohibited * without written permission from Valve LLC. * ****/ #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "squadmonster.h" #include "player.h" #include "weapons.h" #include "decals.h" #include "gamerules.h" #include "effects.h" #include "saverestore.h" #include "ropes.h" #define HOOK_CONSTANT 2500.0f #define SPRING_DAMPING 0.1f #define ROPE_IGNORE_SAMPLES 4 // integrator may be hanging if less than /** * Data for a single rope joint. */ struct RopeSampleData { Vector mPosition; Vector mVelocity; Vector mForce; Vector mExternalForce; bool mApplyExternalForce; float mMassReciprocal; float restLength; }; #define MAX_LIST_SEGMENTS 5 RopeSampleData g_pTempList[MAX_LIST_SEGMENTS][MAX_SEGMENTS]; /** * Represents a single joint in a rope. There are numSegments + 1 samples in a rope. */ class CRopeSample : public CBaseEntity { public: void Spawn(); virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; static CRopeSample* CreateSample(); RopeSampleData& GetData() { return data; } RopeSampleData& GetSourceData() { if (swapped) return data2; else return data; } RopeSampleData& GetTargetData() { if (swapped) return data; else return data2; } void Swap() { swapped = !swapped; } void ResetSwap() { swapped = FALSE; } private: RopeSampleData data; RopeSampleData data2; BOOL swapped; }; class CRopeSegment : public CBaseAnimating { public: void Precache(); void Spawn(); void Touch( CBaseEntity* pOther ); void SetAbsOrigin(const Vector& pos) { pev->origin = pos; } static CRopeSegment* CreateSegment(CRopeSample* pSample, string_t iszModelName , CRope *rope); CRopeSample* GetSample() { return m_Sample; } void ApplyExternalForce( const Vector& vecForce ); void SetCauseDamageOnTouch( const bool bCauseDamage ); void SetCanBeGrabbed( const bool bCanBeGrabbed ); CRope* GetMasterRope() { return mMasterRope; } void SetMasterRope( CRope* pRope ) { mMasterRope = pRope; } virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; private: CRopeSample* m_Sample; string_t mModelName; float mDefaultMass; bool mCauseDamage; bool mCanBeGrabbed; CRope* mMasterRope; }; static const char* const g_pszCreakSounds[] = { "items/rope1.wav", "items/rope2.wav", "items/rope3.wav" }; TYPEDESCRIPTION CRope::m_SaveData[] = { DEFINE_FIELD( CRope, m_iSegments, FIELD_INTEGER ), DEFINE_FIELD( CRope, m_bToggle, FIELD_CHARACTER ), DEFINE_FIELD( CRope, m_InitialDeltaTime, FIELD_CHARACTER ), DEFINE_FIELD( CRope, mLastTime, FIELD_TIME ), DEFINE_FIELD( CRope, m_LastEndPos, FIELD_POSITION_VECTOR ), DEFINE_FIELD( CRope, m_Gravity, FIELD_VECTOR ), DEFINE_FIELD( CRope, m_NumSamples, FIELD_INTEGER ), DEFINE_FIELD( CRope, mObjectAttached, FIELD_CHARACTER ), DEFINE_FIELD( CRope, mAttachedObjectsSegment, FIELD_INTEGER ), DEFINE_FIELD( CRope, detachTime, FIELD_TIME ), DEFINE_ARRAY( CRope, seg, FIELD_CLASSPTR, MAX_SEGMENTS ), DEFINE_ARRAY( CRope, altseg, FIELD_CLASSPTR, MAX_SEGMENTS ), DEFINE_ARRAY( CRope, m_Samples, FIELD_CLASSPTR, MAX_SAMPLES ), DEFINE_FIELD( CRope, mDisallowPlayerAttachment, FIELD_INTEGER ), DEFINE_FIELD( CRope, mBodyModel, FIELD_STRING ), DEFINE_FIELD( CRope, mEndingModel, FIELD_STRING ), DEFINE_FIELD( CRope, mAttachedObjectsOffset, FIELD_FLOAT ), DEFINE_FIELD( CRope, m_bMakeSound, FIELD_CHARACTER ), DEFINE_FIELD( CRope, m_activated, FIELD_CHARACTER ), }; IMPLEMENT_SAVERESTORE( CRope, CBaseDelay ) LINK_ENTITY_TO_CLASS( env_rope, CRope ) void CRope::KeyValue( KeyValueData* pkvd ) { if( FStrEq( pkvd->szKeyName, "segments" ) ) { pkvd->fHandled = true; m_iSegments = strtol( pkvd->szValue, NULL, 10 ); if( m_iSegments >= MAX_SEGMENTS ) m_iSegments = MAX_SEGMENTS - 1; } else if( FStrEq( pkvd->szKeyName, "bodymodel" ) ) { pkvd->fHandled = true; mBodyModel = ALLOC_STRING( pkvd->szValue ); } else if( FStrEq( pkvd->szKeyName, "endingmodel" ) ) { pkvd->fHandled = true; mEndingModel = ALLOC_STRING( pkvd->szValue ); } else if( FStrEq( pkvd->szKeyName, "disable" ) ) { pkvd->fHandled = true; mDisallowPlayerAttachment = strtol( pkvd->szValue, NULL, 10 ); } else CBaseDelay::KeyValue( pkvd ); } CRope::CRope() { mBodyModel = MAKE_STRING( "models/rope16.mdl" ); mEndingModel = MAKE_STRING( "models/rope16.mdl" ); } void CRope::Precache() { CBaseDelay::Precache(); UTIL_PrecacheOther( "rope_segment" ); UTIL_PrecacheOther( "rope_sample" ); PRECACHE_MODEL(STRING(GetBodyModel())); PRECACHE_MODEL(STRING(GetEndingModel())); PRECACHE_SOUND_ARRAY( g_pszCreakSounds ); } void CRope::Spawn() { pev->classname = MAKE_STRING( "env_rope" ); m_bMakeSound = true; Precache(); m_Gravity.x = m_Gravity.y = 0; m_Gravity.z = -50; mObjectAttached = false; m_NumSamples = m_iSegments + 1; m_activated = false; } void CRope::Activate() { if (!m_activated) { InitRope(); m_activated = true; } } void CRope::InitRope() { pev->flags |= FL_ALWAYSTHINK; for( int uiSample = 0; uiSample < m_NumSamples; ++uiSample ) { m_Samples[ uiSample ] = CRopeSample::CreateSample(); UTIL_SetOrigin(m_Samples[ uiSample ]->pev, pev->origin); } memset( m_Samples + m_NumSamples, 0, sizeof( CRopeSample* ) * ( MAX_SAMPLES - m_NumSamples ) ); { CRopeSegment* pSegment = seg[ 0 ] = CRopeSegment::CreateSegment( m_Samples[ 0 ], GetBodyModel(), this ); pSegment->SetAbsOrigin( pev->origin ); pSegment = altseg[ 0 ] = CRopeSegment::CreateSegment( m_Samples[ 0 ], GetBodyModel(), this ); pSegment->SetAbsOrigin( pev->origin ); } Vector origin; Vector angles; const Vector vecGravity = m_Gravity.Normalize(); if( m_iSegments > 2 ) { CRopeSample** ppCurrentSys = m_Samples; for( int uiSeg = 1; uiSeg < m_iSegments - 1; ++uiSeg ) { CRopeSample* pSegSample = m_Samples[ uiSeg ]; seg[ uiSeg ] = CRopeSegment::CreateSegment( pSegSample, GetBodyModel(), this ); altseg[ uiSeg ] = CRopeSegment::CreateSegment( pSegSample, GetBodyModel(), this ); CRopeSegment* pCurrent = seg[ uiSeg - 1 ]; pCurrent->GetAttachment( 0, origin, angles ); Vector vecPos = origin - pCurrent->pev->origin; const float flLength = vecPos.Length(); origin = flLength * vecGravity + pCurrent->pev->origin; seg[ uiSeg ]->SetAbsOrigin( origin ); altseg[ uiSeg ]->SetAbsOrigin( origin ); } } CRopeSample* pSegSample = m_Samples[ m_iSegments - 1 ]; seg[ m_iSegments - 1 ] = CRopeSegment::CreateSegment( pSegSample, GetEndingModel(), this ); altseg[ m_iSegments - 1 ] = CRopeSegment::CreateSegment( pSegSample, GetEndingModel(), this ); CRopeSegment* pCurrent = seg[ m_iSegments - 2 ]; pCurrent->GetAttachment( 0, origin, angles ); Vector vecPos = origin - pCurrent->pev->origin; const float flLength = vecPos.Length(); origin = flLength * vecGravity + pCurrent->pev->origin; seg[ m_iSegments - 1 ]->SetAbsOrigin( origin ); altseg[ m_iSegments - 1 ]->SetAbsOrigin( origin ); memset( seg + m_iSegments, 0, sizeof( CRopeSegment* ) * ( MAX_SEGMENTS - m_iSegments ) ); memset( altseg + m_iSegments, 0, sizeof( CRopeSegment* ) * ( MAX_SEGMENTS - m_iSegments ) ); m_InitialDeltaTime = true; InitializeRopeSim(); SetThink(&CRope::RopeThink); pev->nextthink = gpGlobals->time + 0.01; } void CRope::RopeThink() { m_bToggle = !m_bToggle; RunSimOnSamples(); CRopeSegment** ppPrimarySegs; CRopeSegment** ppHiddenSegs; if( m_bToggle ) { ppPrimarySegs = altseg; ppHiddenSegs = seg; } else { ppPrimarySegs = seg; ppHiddenSegs = altseg; } SetRopeSegments( m_iSegments, ppPrimarySegs, ppHiddenSegs ); if( ShouldCreak() ) { Creak(); } pev->nextthink = gpGlobals->time + 0.001; } void CRope::InitializeRopeSim() { int uiIndex; for( int uiSeg = 0; uiSeg < m_iSegments; ++uiSeg ) { CRopeSegment* pSegment = seg[ uiSeg ]; CRopeSample* pSample = pSegment->GetSample(); RopeSampleData& data = pSample->GetData(); data.mPosition = pSegment->pev->origin; data.mVelocity = g_vecZero; data.mForce = g_vecZero; data.mMassReciprocal = 1; data.mApplyExternalForce = false; data.mExternalForce = g_vecZero; Vector vecOrigin, vecAngles; pSegment->GetAttachment( 0, vecOrigin, vecAngles ); data.restLength = ( pSegment->pev->origin - vecOrigin ).Length(); } { //Zero out the anchored segment's mass so it stays in place. CRopeSample *pSample = m_Samples[ 0 ]; pSample->GetData().mMassReciprocal = 0; } CRopeSegment* pSegment = seg[ m_iSegments - 1 ]; Vector vecOrigin, vecAngles; pSegment->GetAttachment( 0, vecOrigin, vecAngles ); Vector vecDistance = vecOrigin - pSegment->pev->origin; const float flLength = vecDistance.Length(); const Vector vecGravity = m_Gravity.Normalize(); vecOrigin = vecGravity * flLength + pSegment->pev->origin; CRopeSample* pSample = m_Samples[ m_NumSamples - 1 ]; RopeSampleData& data = pSample->GetData(); data.mPosition = vecOrigin; m_LastEndPos = vecOrigin; data.mVelocity = g_vecZero; data.mForce = g_vecZero; data.mMassReciprocal = 0.2; data.mApplyExternalForce = false; int uiNumSegs = ROPE_IGNORE_SAMPLES; if( m_iSegments <= ROPE_IGNORE_SAMPLES ) uiNumSegs = m_iSegments; for( uiIndex = 0; uiIndex < uiNumSegs; ++uiIndex ) { seg[ uiIndex ]->SetCanBeGrabbed( false ); altseg[ uiIndex ]->SetCanBeGrabbed( false ); } } void CRope::RunSimOnSamples() { float flDeltaTime = 0.025; if( m_InitialDeltaTime ) { m_InitialDeltaTime = false; mLastTime = gpGlobals->time; flDeltaTime = 0; } int uiIndex = 0; bool swapped = false; while( true ) { ++uiIndex; ComputeForces( m_Samples ); RK4Integrate( flDeltaTime ); mLastTime += 0.007; if( gpGlobals->time <= mLastTime ) { if( ( uiIndex % 2 ) != 0 ) break; } for (int i=0; iSwap(); } swapped = !swapped; } if (swapped) { for (int i=0; iResetSwap(); } } mLastTime = gpGlobals->time; } void CRope::ComputeForces( RopeSampleData* pSystem ) { int uiIndex; for( uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex ) { ComputeSampleForce( pSystem[ uiIndex ] ); } for( uiIndex = 0; uiIndex < m_iSegments; ++uiIndex ) { ComputeSpringForce( pSystem[ uiIndex ], pSystem[ uiIndex+1 ] ); } } void CRope::ComputeForces( CRopeSample** ppSystem ) { int uiIndex; for( uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex ) { ComputeSampleForce( ppSystem[ uiIndex ]->GetSourceData() ); } for( uiIndex = 0; uiIndex < m_iSegments; ++uiIndex ) { ComputeSpringForce( ppSystem[ uiIndex ]->GetSourceData(), ppSystem[ uiIndex+1 ]->GetSourceData() ); } } void CRope::ComputeSampleForce( RopeSampleData& data ) { data.mForce = g_vecZero; if( data.mMassReciprocal != 0.0 ) { data.mForce = data.mForce + ( m_Gravity / data.mMassReciprocal ); } if( data.mApplyExternalForce ) { data.mForce = data.mForce + data.mExternalForce; data.mExternalForce = g_vecZero; data.mApplyExternalForce = false; } if( DotProduct( m_Gravity, data.mVelocity ) >= 0 ) { data.mForce = data.mForce + data.mVelocity * -0.04; } else { data.mForce = data.mForce - data.mVelocity; } } void CRope::ComputeSpringForce( RopeSampleData& first, RopeSampleData& second ) { Vector vecDist = first.mPosition - second.mPosition; const double flDistance = vecDist.Length(); const double flForce = ( flDistance - first.restLength ) * HOOK_CONSTANT; const double flNewRelativeDist = DotProduct( first.mVelocity - second.mVelocity, vecDist ) * SPRING_DAMPING; vecDist = vecDist.Normalize(); const double flSpringFactor = -( flNewRelativeDist / flDistance + flForce ); const Vector vecForce = flSpringFactor * vecDist; first.mForce = first.mForce + vecForce; second.mForce = second.mForce - vecForce; } void CRope::RK4Integrate( const float flDeltaTime ) { const float flDeltas[ MAX_LIST_SEGMENTS - 1 ] = { flDeltaTime * 0.5f, flDeltaTime * 0.5f, flDeltaTime * 0.5f, flDeltaTime }; { RopeSampleData* pTemp1 = g_pTempList[ 0 ]; RopeSampleData* pTemp2 = g_pTempList[ 1 ]; for( int uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex, ++pTemp1, ++pTemp2 ) { RopeSampleData& data = m_Samples[ uiIndex ]->GetSourceData(); pTemp2->mForce = data.mMassReciprocal * data.mForce * flDeltas[ 0 ]; pTemp2->mVelocity = data.mVelocity * flDeltas[ 0 ]; pTemp2->restLength = data.restLength; pTemp1->mMassReciprocal = data.mMassReciprocal; pTemp1->mVelocity = data.mVelocity + pTemp2->mForce; pTemp1->mPosition = data.mPosition + pTemp2->mVelocity; pTemp1->restLength = data.restLength; } ComputeForces( g_pTempList[ 0 ] ); } for( int uiStep = 2; uiStep < MAX_LIST_SEGMENTS - 1; ++uiStep ) { RopeSampleData* pTemp1 = g_pTempList[ 0 ]; RopeSampleData* pTemp2 = g_pTempList[ uiStep ]; for( int uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex, ++pTemp1, ++pTemp2 ) { RopeSampleData& data = m_Samples[ uiIndex ]->GetSourceData(); pTemp2->mForce = data.mMassReciprocal * pTemp1->mForce * flDeltas[ uiStep - 1 ]; pTemp2->mVelocity = pTemp1->mVelocity * flDeltas[ uiStep - 1 ]; pTemp2->restLength = data.restLength; pTemp1->mMassReciprocal = data.mMassReciprocal; pTemp1->mVelocity = data.mVelocity + pTemp2->mForce; pTemp1->mPosition = data.mPosition + pTemp2->mVelocity; pTemp1->restLength = data.restLength; } ComputeForces( g_pTempList[ 0 ] ); } { RopeSampleData* pTemp1 = g_pTempList[ 0 ]; RopeSampleData* pTemp2 = g_pTempList[ 4 ]; for( int uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex, ++pTemp1, ++pTemp2 ) { RopeSampleData& data = m_Samples[ uiIndex ]->GetSourceData(); pTemp2->mForce = data.mMassReciprocal * pTemp1->mForce * flDeltas[ 3 ]; pTemp2->mVelocity = pTemp1->mVelocity * flDeltas[ 3 ]; } } RopeSampleData* pTemp1 = g_pTempList[ 1 ]; RopeSampleData* pTemp2 = g_pTempList[ 2 ]; RopeSampleData* pTemp3 = g_pTempList[ 3 ]; RopeSampleData* pTemp4 = g_pTempList[ 4 ]; for( int uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex, ++pTemp1, ++pTemp2, ++pTemp3, ++pTemp4 ) { RopeSampleData& pSource = m_Samples[ uiIndex ]->GetSourceData(); RopeSampleData& pTarget = m_Samples[ uiIndex ]->GetTargetData(); const Vector vecPosChange = 1.0f / 6.0f * ( pTemp1->mVelocity + ( pTemp2->mVelocity + pTemp3->mVelocity ) * 2 + pTemp4->mVelocity ); const Vector vecVelChange = 1.0f / 6.0f * ( pTemp1->mForce + ( pTemp2->mForce + pTemp3->mForce ) * 2 + pTemp4->mForce ); pTarget.mPosition = pSource.mPosition + ( vecPosChange );//* flDeltaTime ); pTarget.mVelocity = pSource.mVelocity + ( vecVelChange );//* flDeltaTime ); } } //TODO move to common header - Solokiller static const Vector DOWN( 0, 0, -1 ); static const Vector RIGHT( 0, 1, 0 ); void GetAlignmentAngles( const Vector& vecTop, const Vector& vecBottom, Vector& vecOut ) { Vector vecDist = vecBottom - vecTop; Vector vecResult = vecDist.Normalize(); const float flRoll = acos( DotProduct( vecResult, RIGHT ) ) * ( 180.0 / M_PI ); vecOut.z = -flRoll; vecDist.y = 0; vecResult = vecDist.Normalize(); const float flPitch = acos( DotProduct( vecResult, DOWN ) ) * ( 180.0 / M_PI ); vecOut.x = ( vecResult.x >= 0.0 ) ? flPitch : -flPitch; vecOut.y = 0; } void TruncateEpsilon( Vector& vec ) { Vector vec1 = vec * 10.0; vec1.x += 0.5; vec = vec1 / 10; } void CRope::TraceModels( CRopeSegment** ppPrimarySegs, CRopeSegment** ppHiddenSegs ) { if( m_iSegments > 1 ) { Vector vecAngles; GetAlignmentAngles( m_Samples[ 0 ]->GetData().mPosition, m_Samples[ 1 ]->GetData().mPosition, vecAngles ); ( *ppPrimarySegs )->pev->angles = vecAngles; } TraceResult tr; if( mObjectAttached ) { for( unsigned int uiSeg = 1; uiSeg < m_iSegments; ++uiSeg ) { CRopeSample* pSample = m_Samples[ uiSeg ]; Vector vecDist = pSample->GetData().mPosition - ppHiddenSegs[ uiSeg ]->pev->origin; vecDist = vecDist.Normalize(); // HACK: this code relies on integer underflow. uiSeg must be unsigned! const float flTraceDist = ( uiSeg - mAttachedObjectsSegment + 2 ) < 5 ? 50 : 10; const Vector vecTraceDist = vecDist * flTraceDist; const Vector vecEnd = pSample->GetData().mPosition + vecTraceDist; UTIL_TraceLine( ppHiddenSegs[ uiSeg ]->pev->origin, vecEnd, ignore_monsters, edict(), &tr ); if( tr.flFraction == 1.0 && tr.fAllSolid ) { break; } if( tr.flFraction != 1.0 || tr.fStartSolid || !tr.fInOpen ) { Vector vecOrigin = tr.vecEndPos - vecTraceDist; TruncateEpsilon( vecOrigin ); ppPrimarySegs[ uiSeg ]->SetAbsOrigin( vecOrigin ); Vector vecNormal = tr.vecPlaneNormal.Normalize() * 20000.0; RopeSampleData& data = ppPrimarySegs[ uiSeg ]->GetSample()->GetData(); data.mApplyExternalForce = true; data.mExternalForce = vecNormal; data.mVelocity = g_vecZero; } else { Vector vecOrigin = pSample->GetData().mPosition; TruncateEpsilon( vecOrigin ); ppPrimarySegs[ uiSeg ]->SetAbsOrigin( vecOrigin ); } } } else { for( unsigned int uiSeg = 1; uiSeg < m_iSegments; ++uiSeg ) { UTIL_TraceLine( ppHiddenSegs[ uiSeg ]->pev->origin, m_Samples[ uiSeg ]->GetData().mPosition, ignore_monsters, edict(), &tr ); if( tr.flFraction == 1.0 ) { Vector vecOrigin = m_Samples[ uiSeg ]->GetData().mPosition; TruncateEpsilon( vecOrigin ); ppPrimarySegs[ uiSeg ]->SetAbsOrigin( vecOrigin ); } else { CBaseEntity* pEnt = (CBaseEntity*)GET_PRIVATE( tr.pHit ); const Vector vecNormal = tr.vecPlaneNormal.Normalize(); Vector vecOrigin = tr.vecEndPos + vecNormal * 10.0; TruncateEpsilon( vecOrigin ); ppPrimarySegs[ uiSeg ]->SetAbsOrigin( vecOrigin ); ppPrimarySegs[ uiSeg ]->GetSample()->GetData().mApplyExternalForce = true; ppPrimarySegs[ uiSeg ]->GetSample()->GetData().mExternalForce = vecNormal * 40000.0; } } } Vector vecAngles; for( int uiSeg = 1; uiSeg < m_iSegments; ++uiSeg ) { CRopeSegment *pSegment = ppPrimarySegs[ uiSeg - 1 ]; CRopeSegment *pSegment2 = ppPrimarySegs[ uiSeg ]; GetAlignmentAngles( pSegment->pev->origin, pSegment2->pev->origin, vecAngles ); pSegment->pev->angles = vecAngles; } if( m_iSegments > 1 ) { CRopeSample *pSample = m_Samples[ m_NumSamples - 1 ]; UTIL_TraceLine( m_LastEndPos, pSample->GetData().mPosition, ignore_monsters, edict(), &tr ); if( tr.flFraction == 1.0 ) { m_LastEndPos = pSample->GetData().mPosition; } else { m_LastEndPos = tr.vecEndPos; pSample->GetData().mApplyExternalForce = true; pSample->GetData().mExternalForce = tr.vecPlaneNormal.Normalize() * 40000.0; } CRopeSegment *pSegment = ppPrimarySegs[ m_NumSamples - 2 ]; Vector vecAngles; GetAlignmentAngles( pSegment->pev->origin, m_LastEndPos, vecAngles ); pSegment->pev->angles = vecAngles; } } void CRope::SetRopeSegments( const int uiNumSegments, CRopeSegment** ppPrimarySegs, CRopeSegment** ppHiddenSegs ) { if( uiNumSegments > 0 ) { TraceModels( ppPrimarySegs, ppHiddenSegs ); ppPrimarySegs[ 0 ]->pev->solid = SOLID_TRIGGER; ppPrimarySegs[ 0 ]->pev->effects = 0; ppHiddenSegs[ 0 ]->pev->solid = SOLID_NOT; ppHiddenSegs[ 0 ]->pev->effects = EF_NODRAW; for( int uiIndex = 1; uiIndex < uiNumSegments; ++uiIndex ) { CRopeSegment* pPrim = ppPrimarySegs[ uiIndex ]; CRopeSegment* pHidden = ppHiddenSegs[ uiIndex ]; pPrim->pev->solid = SOLID_TRIGGER; pPrim->pev->effects = 0; pHidden->pev->solid = SOLID_NOT; pHidden->pev->effects = EF_NODRAW; Vector vecOrigin = pPrim->pev->origin; //vecOrigin.x += 10.0; //vecOrigin.y += 10.0; pHidden->SetAbsOrigin( vecOrigin ); } } } bool CRope::MoveUp( const float flDeltaTime ) { if( mAttachedObjectsSegment > 4 ) { float flDistance = flDeltaTime * 128.0; Vector vecOrigin, vecAngles; while( true ) { float flOldDist = flDistance; flDistance = 0; if( flOldDist <= 0 ) break; if( mAttachedObjectsSegment <= 3 ) break; if( flOldDist > mAttachedObjectsOffset ) { flDistance = flOldDist - mAttachedObjectsOffset; --mAttachedObjectsSegment; float flNewOffset = 0; if( mAttachedObjectsSegment < m_iSegments ) { CRopeSegment *pSegment = seg[ mAttachedObjectsSegment ]; pSegment->GetAttachment( 0, vecOrigin, vecAngles ); flNewOffset = ( pSegment->pev->origin - vecOrigin ).Length(); } mAttachedObjectsOffset = flNewOffset; } else { mAttachedObjectsOffset -= flOldDist; } } } return true; } bool CRope::MoveDown( const float flDeltaTime ) { if( !mObjectAttached ) return false; float flDistance = flDeltaTime * 128.0; Vector vecOrigin, vecAngles; CRopeSegment* pSegment; bool bOnRope = true; bool bDoIteration = true; while( bDoIteration ) { bDoIteration = false; if( flDistance > 0.0 ) { float flNewDist = flDistance; float flSegLength = 0.0; while( bOnRope ) { if( mAttachedObjectsSegment < m_iSegments ) { pSegment = seg[ mAttachedObjectsSegment ]; pSegment->GetAttachment( 0, vecOrigin, vecAngles ); flSegLength = ( pSegment->pev->origin - vecOrigin ).Length(); } const float flOffset = flSegLength - mAttachedObjectsOffset; if( flNewDist <= flOffset ) { mAttachedObjectsOffset += flNewDist; flDistance = 0; bDoIteration = true; break; } if( mAttachedObjectsSegment + 1 == m_iSegments ) bOnRope = false; else ++mAttachedObjectsSegment; flNewDist -= flOffset; flSegLength = 0; mAttachedObjectsOffset = 0; if( flNewDist <= 0 ) break; } } } return bOnRope; } Vector CRope::GetAttachedObjectsVelocity() const { if( !mObjectAttached ) return g_vecZero; return seg[ mAttachedObjectsSegment ]->GetSample()->GetData().mVelocity; } void CRope::ApplyForceFromPlayer( const Vector& vecForce ) { if( !mObjectAttached ) return; float flForce = 20000.0; if( m_iSegments < 26 ) flForce *= ( m_iSegments / 26.0 ); const Vector vecScaledForce = vecForce * flForce; ApplyForceToSegment( vecScaledForce, mAttachedObjectsSegment ); } void CRope::ApplyForceToSegment( const Vector& vecForce, const int uiSegment ) { if( uiSegment < m_iSegments ) { seg[ uiSegment ]->ApplyExternalForce( vecForce ); } else if( uiSegment == m_iSegments ) { //Apply force to the last sample. RopeSampleData& data = m_Samples[ uiSegment - 1 ]->GetData(); data.mExternalForce = data.mExternalForce + vecForce; data.mApplyExternalForce = true; } } void CRope::AttachObjectToSegment( CRopeSegment* pSegment ) { mObjectAttached = true; detachTime = 0; SetAttachedObjectsSegment( pSegment ); mAttachedObjectsOffset = 0; } void CRope::DetachObject() { mObjectAttached = false; detachTime = gpGlobals->time; } bool CRope::IsAcceptingAttachment() const { if( gpGlobals->time - detachTime > 2.0 && !mObjectAttached ) { return mDisallowPlayerAttachment != 1; } return false; } bool CRope::ShouldCreak() const { if( mObjectAttached && m_bMakeSound ) { CRopeSample* pSample = seg[ mAttachedObjectsSegment ]->GetSample(); if( pSample->GetData().mVelocity.Length() > 20.0 ) return RANDOM_LONG( 1, 5 ) == 1; } return false; } void CRope::Creak() { EMIT_SOUND( edict(), CHAN_BODY, g_pszCreakSounds[ RANDOM_LONG( 0, ARRAYSIZE( g_pszCreakSounds ) - 1 ) ], VOL_NORM, ATTN_NORM ); } float CRope::GetSegmentLength( int uiSegmentIndex ) const { if( uiSegmentIndex < m_iSegments ) { Vector vecOrigin, vecAngles; CRopeSegment *pSegment = seg[ uiSegmentIndex ]; pSegment->GetAttachment( 0, vecOrigin, vecAngles ); return ( pSegment->pev->origin - vecOrigin ).Length(); } return 0; } float CRope::GetRopeLength() const { float flLength = 0; Vector vecOrigin, vecAngles; for( int uiIndex = 0; uiIndex < m_iSegments; ++uiIndex ) { CRopeSegment *pSegment = seg[ uiIndex ]; pSegment->GetAttachment( 0, vecOrigin, vecAngles ); flLength += ( pSegment->pev->origin - vecOrigin ).Length(); } return flLength; } Vector CRope::GetRopeOrigin() const { return m_Samples[ 0 ]->GetData().mPosition; } bool CRope::IsValidSegmentIndex( const int uiSegment ) const { return uiSegment < m_iSegments; } Vector CRope::GetSegmentOrigin( const int uiSegment ) const { if( !IsValidSegmentIndex( uiSegment ) ) return g_vecZero; return m_Samples[ uiSegment ]->GetData().mPosition; } Vector CRope::GetSegmentAttachmentPoint( const int uiSegment ) const { if( !IsValidSegmentIndex( uiSegment ) ) return g_vecZero; Vector vecOrigin, vecAngles; CRopeSegment *pSegment = m_bToggle ? altseg[ uiSegment ] : seg[ uiSegment ]; pSegment->GetAttachment( 0, vecOrigin, vecAngles ); return vecOrigin; } void CRope::SetAttachedObjectsSegment( CRopeSegment* pSegment ) { for( int uiIndex = 0; uiIndex < m_iSegments; ++uiIndex ) { if( seg[ uiIndex ] == pSegment || altseg[ uiIndex ] == pSegment ) { mAttachedObjectsSegment = uiIndex; break; } } } Vector CRope::GetSegmentDirFromOrigin( const int uiSegmentIndex ) const { if( uiSegmentIndex >= m_iSegments ) return g_vecZero; //There is one more sample than there are segments, so this is fine. const Vector vecResult = m_Samples[ uiSegmentIndex + 1 ]->GetData().mPosition - m_Samples[ uiSegmentIndex ]->GetData().mPosition; return vecResult.Normalize(); } Vector CRope::GetAttachedObjectsPosition() const { if( !mObjectAttached ) return g_vecZero; Vector vecResult; if( mAttachedObjectsSegment < m_iSegments ) vecResult = m_Samples[ mAttachedObjectsSegment ]->GetData().mPosition; vecResult = vecResult + ( mAttachedObjectsOffset * GetSegmentDirFromOrigin( mAttachedObjectsSegment ) ); return vecResult; } // Global Savedata for player TYPEDESCRIPTION CRopeSample::m_SaveData[] = { DEFINE_FIELD( CRopeSample, data.mPosition, FIELD_VECTOR ), DEFINE_FIELD( CRopeSample, data.mVelocity, FIELD_VECTOR ), DEFINE_FIELD( CRopeSample, data.mForce, FIELD_VECTOR ), DEFINE_FIELD( CRopeSample, data.mExternalForce, FIELD_VECTOR ), DEFINE_FIELD( CRopeSample, data.mApplyExternalForce, FIELD_CHARACTER ), DEFINE_FIELD( CRopeSample, data.mMassReciprocal, FIELD_FLOAT ), DEFINE_FIELD( CRopeSample, data.restLength, FIELD_FLOAT ), DEFINE_FIELD( CRopeSample, data2.mPosition, FIELD_VECTOR ), DEFINE_FIELD( CRopeSample, data2.mVelocity, FIELD_VECTOR ), DEFINE_FIELD( CRopeSample, data2.mForce, FIELD_VECTOR ), DEFINE_FIELD( CRopeSample, data2.mExternalForce, FIELD_VECTOR ), DEFINE_FIELD( CRopeSample, data2.mApplyExternalForce, FIELD_CHARACTER ), DEFINE_FIELD( CRopeSample, data2.mMassReciprocal, FIELD_FLOAT ), DEFINE_FIELD( CRopeSample, data2.restLength, FIELD_FLOAT ), DEFINE_FIELD( CRopeSample, swapped, FIELD_BOOLEAN ), }; IMPLEMENT_SAVERESTORE(CRopeSample, CBaseEntity) LINK_ENTITY_TO_CLASS( rope_sample, CRopeSample ); void CRopeSample::Spawn() { pev->classname = MAKE_STRING( "rope_sample" ); pev->effects |= EF_NODRAW; } CRopeSample* CRopeSample::CreateSample() { CRopeSample* pSample = GetClassPtr( NULL ); pSample->Spawn(); return pSample; } TYPEDESCRIPTION CRopeSegment::m_SaveData[] = { DEFINE_FIELD( CRopeSegment, m_Sample, FIELD_CLASSPTR ), DEFINE_FIELD( CRopeSegment, mModelName, FIELD_STRING ), DEFINE_FIELD( CRopeSegment, mDefaultMass, FIELD_FLOAT ), DEFINE_FIELD( CRopeSegment, mCauseDamage, FIELD_CHARACTER ), DEFINE_FIELD( CRopeSegment, mCanBeGrabbed, FIELD_CHARACTER ), DEFINE_FIELD( CRopeSegment, mMasterRope, FIELD_CLASSPTR ), }; IMPLEMENT_SAVERESTORE( CRopeSegment, CBaseAnimating ) LINK_ENTITY_TO_CLASS( rope_segment, CRopeSegment ); void CRopeSegment::Precache() { CBaseAnimating::Precache(); if( !mModelName ) mModelName = MAKE_STRING( "models/rope16.mdl" ); PRECACHE_MODEL( STRING( mModelName ) ); PRECACHE_SOUND( "items/grab_rope.wav" ); } void CRopeSegment::Spawn() { pev->classname = MAKE_STRING( "rope_segment" ); Precache(); SET_MODEL( edict(), STRING( mModelName ) ); pev->movetype = MOVETYPE_NOCLIP; pev->solid = SOLID_TRIGGER; pev->effects = EF_NODRAW; SetAbsOrigin( pev->origin ); UTIL_SetSize( pev, Vector( -30, -30, -30 ), Vector( 30, 30, 30 ) ); pev->nextthink = gpGlobals->time + 0.5; } void CRopeSegment::Touch( CBaseEntity* pOther ) { if( pOther->IsPlayer() ) { CBasePlayer *pPlayer = static_cast( pOther ); //Electrified wires deal damage. - Solokiller if( mCauseDamage ) { pOther->TakeDamage( pev, pev, 1, DMG_SHOCK ); } if (pPlayer->m_afPhysicsFlags & PFLAG_ONBARNACLE) return; if( GetMasterRope()->IsAcceptingAttachment() && !(pPlayer->m_afPhysicsFlags & PFLAG_ONROPE) ) { if( mCanBeGrabbed ) { RopeSampleData& data = m_Sample->GetData(); pOther->pev->origin = data.mPosition; pPlayer->SetOnRopeState( true ); pPlayer->SetRope( GetMasterRope() ); GetMasterRope()->AttachObjectToSegment( this ); const Vector& vecVelocity = pOther->pev->velocity; if( vecVelocity.Length() > 0.5 ) { //Apply some external force to move the rope. - Solokiller data.mApplyExternalForce = true; data.mExternalForce = data.mExternalForce + vecVelocity * 750; } if( GetMasterRope()->IsSoundAllowed() ) { EMIT_SOUND( edict(), CHAN_BODY, "items/grab_rope.wav", 1.0, ATTN_NORM ); } } else { //This segment cannot be grabbed, so grab the highest one if possible. - Solokiller CRope *pRope = GetMasterRope(); CRopeSegment* pSegment; if( pRope->GetNumSegments() <= 4 ) { //Fewer than 5 segments exist, so allow grabbing the last one. - Solokiller pSegment = pRope->GetSegments()[ pRope->GetNumSegments() - 1 ]; pSegment->SetCanBeGrabbed( true ); } else { pSegment = pRope->GetSegments()[ 4 ]; } pSegment->Touch( pOther ); } } } } CRopeSegment* CRopeSegment::CreateSegment( CRopeSample* pSample, string_t iszModelName, CRope* rope ) { CRopeSegment* pSegment = GetClassPtr( NULL ); pSegment->mModelName = iszModelName; pSegment->Spawn(); pSegment->m_Sample = pSample; pSegment->mCauseDamage = false; pSegment->mCanBeGrabbed = true; pSegment->mDefaultMass = pSample->GetData().mMassReciprocal; pSegment->SetMasterRope(rope); return pSegment; } void CRopeSegment::ApplyExternalForce( const Vector& vecForce ) { m_Sample->GetData().mApplyExternalForce = true; m_Sample->GetData().mExternalForce = m_Sample->GetData().mExternalForce + vecForce; } void CRopeSegment::SetCauseDamageOnTouch( const bool bCauseDamage ) { mCauseDamage = bCauseDamage; } void CRopeSegment::SetCanBeGrabbed( const bool bCanBeGrabbed ) { mCanBeGrabbed = bCanBeGrabbed; } class CElectrifiedWire : public CRope { public: CElectrifiedWire(); void InitElectrifiedRope(); virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; void KeyValue( KeyValueData* pkvd ); void Precache(); void Spawn(); void Activate(); void EXPORT ElectrifiedRopeThink(); void Use( CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float flValue ); /** * @return Whether the wire is active. */ bool IsActive() const { return m_bIsActive; } /** * @param iFrequency Frequency. * @return Whether the spark effect should be performed. */ bool ShouldDoEffect( const int iFrequency ); /** * Do spark effects. */ void DoSpark( const int uiSegment, const bool bExertForce ); /** * Do lightning effects. */ void DoLightning(); private: bool m_bIsActive; int m_iTipSparkFrequency; int m_iBodySparkFrequency; int m_iLightningFrequency; int m_iXJoltForce; int m_iYJoltForce; int m_iZJoltForce; int m_uiNumUninsulatedSegments; int m_uiUninsulatedSegments[ MAX_SEGMENTS ]; int m_iLightningSprite; float m_flLastSparkTime; }; CElectrifiedWire::CElectrifiedWire() { m_bIsActive = true; m_iTipSparkFrequency = 3; m_iBodySparkFrequency = 100; m_iLightningFrequency = 150; m_iXJoltForce = 0; m_iYJoltForce = 0; m_iZJoltForce = 0; m_uiNumUninsulatedSegments = 0; } TYPEDESCRIPTION CElectrifiedWire::m_SaveData[] = { DEFINE_FIELD( CElectrifiedWire, m_bIsActive, FIELD_CHARACTER ), DEFINE_FIELD( CElectrifiedWire, m_iTipSparkFrequency, FIELD_INTEGER ), DEFINE_FIELD( CElectrifiedWire, m_iBodySparkFrequency, FIELD_INTEGER ), DEFINE_FIELD( CElectrifiedWire, m_iLightningFrequency, FIELD_INTEGER ), DEFINE_FIELD( CElectrifiedWire, m_iXJoltForce, FIELD_INTEGER ), DEFINE_FIELD( CElectrifiedWire, m_iYJoltForce, FIELD_INTEGER ), DEFINE_FIELD( CElectrifiedWire, m_iZJoltForce, FIELD_INTEGER ), DEFINE_FIELD( CElectrifiedWire, m_uiNumUninsulatedSegments, FIELD_INTEGER ), DEFINE_ARRAY( CElectrifiedWire, m_uiUninsulatedSegments, FIELD_INTEGER, MAX_SEGMENTS ), //DEFINE_FIELD( m_iLightningSprite, FIELD_INTEGER ), //Not restored, reset in Precache. - Solokiller DEFINE_FIELD( CElectrifiedWire, m_flLastSparkTime, FIELD_TIME ), }; LINK_ENTITY_TO_CLASS( env_electrified_wire, CElectrifiedWire ); IMPLEMENT_SAVERESTORE( CElectrifiedWire, CRope ); void CElectrifiedWire::KeyValue( KeyValueData* pkvd ) { if( FStrEq( pkvd->szKeyName, "sparkfrequency" ) ) { m_iTipSparkFrequency = strtol( pkvd->szValue, NULL, 10 ); pkvd->fHandled = true; } else if( FStrEq( pkvd->szKeyName, "bodysparkfrequency" ) ) { m_iBodySparkFrequency = strtol( pkvd->szValue, NULL, 10 ); pkvd->fHandled = true; } else if( FStrEq( pkvd->szKeyName, "lightningfrequency" ) ) { m_iLightningFrequency = strtol( pkvd->szValue, NULL, 10 ); pkvd->fHandled = true; } else if( FStrEq( pkvd->szKeyName, "xforce" ) ) { m_iXJoltForce = strtol( pkvd->szValue, NULL, 10 ); pkvd->fHandled = true; } else if( FStrEq( pkvd->szKeyName, "yforce" ) ) { m_iYJoltForce = strtol( pkvd->szValue, NULL, 10 ); pkvd->fHandled = true; } else if( FStrEq( pkvd->szKeyName, "zforce" ) ) { m_iZJoltForce = strtol( pkvd->szValue, NULL, 10 ); pkvd->fHandled = true; } else CRope::KeyValue( pkvd ); } void CElectrifiedWire::Precache() { CRope::Precache(); m_iLightningSprite = PRECACHE_MODEL( "sprites/lgtning.spr" ); } void CElectrifiedWire::Spawn() { CRope::Spawn(); pev->classname = MAKE_STRING( "env_electrified_wire" ); } void CElectrifiedWire::Activate() { if (!m_activated) { InitElectrifiedRope(); m_activated = true; } } void CElectrifiedWire::InitElectrifiedRope() { InitRope(); m_uiNumUninsulatedSegments = 0; m_bIsActive = true; if( m_iBodySparkFrequency > 0 ) { for( int uiIndex = 0; uiIndex < GetNumSegments(); ++uiIndex ) { if( IsValidSegmentIndex( uiIndex ) ) { m_uiUninsulatedSegments[ m_uiNumUninsulatedSegments++ ] = uiIndex; } } } if( m_uiNumUninsulatedSegments > 0 ) { for( int uiIndex = 0; uiIndex < m_uiNumUninsulatedSegments; ++uiIndex ) { GetSegments()[ uiIndex ]->SetCauseDamageOnTouch( m_bIsActive ); GetAltSegments()[ uiIndex ]->SetCauseDamageOnTouch( m_bIsActive ); } } if( m_iTipSparkFrequency > 0 ) { GetSegments()[ GetNumSegments() - 1 ]->SetCauseDamageOnTouch( m_bIsActive ); GetAltSegments()[ GetNumSegments() - 1 ]->SetCauseDamageOnTouch( m_bIsActive ); } m_flLastSparkTime = gpGlobals->time; SetSoundAllowed( false ); pev->nextthink = gpGlobals->time + 0.01; SetThink(&CElectrifiedWire::ElectrifiedRopeThink); } void CElectrifiedWire::ElectrifiedRopeThink() { if( gpGlobals->time - m_flLastSparkTime > 0.1 ) { m_flLastSparkTime = gpGlobals->time; if( m_uiNumUninsulatedSegments > 0 ) { for( int uiIndex = 0; uiIndex < m_uiNumUninsulatedSegments; ++uiIndex ) { if( ShouldDoEffect( m_iBodySparkFrequency ) ) { DoSpark( m_uiUninsulatedSegments[ uiIndex ], false ); } } } if( ShouldDoEffect( m_iTipSparkFrequency ) ) { DoSpark( GetNumSegments() - 1, true ); } if( ShouldDoEffect( m_iLightningFrequency ) ) DoLightning(); } CRope::RopeThink(); } void CElectrifiedWire::Use( CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float flValue ) { m_bIsActive = !m_bIsActive; if( m_uiNumUninsulatedSegments > 0 ) { for( int uiIndex = 0; uiIndex < m_uiNumUninsulatedSegments; ++uiIndex ) { GetSegments()[ m_uiUninsulatedSegments[ uiIndex ] ]->SetCauseDamageOnTouch( m_bIsActive ); GetAltSegments()[ m_uiUninsulatedSegments[ uiIndex ] ]->SetCauseDamageOnTouch( m_bIsActive ); } } if( m_iTipSparkFrequency > 0 ) { GetSegments()[ GetNumSegments() - 1 ]->SetCauseDamageOnTouch( m_bIsActive ); GetAltSegments()[ GetNumSegments() - 1 ]->SetCauseDamageOnTouch( m_bIsActive ); } } bool CElectrifiedWire::ShouldDoEffect( const int iFrequency ) { if( iFrequency <= 0 ) return false; if( !IsActive() ) return false; return RANDOM_LONG( 1, iFrequency ) == 1; } void CElectrifiedWire::DoSpark( const int uiSegment, const bool bExertForce ) { const Vector vecOrigin = GetSegmentAttachmentPoint( uiSegment ); UTIL_Sparks( vecOrigin ); if( bExertForce ) { const Vector vecSparkForce( RANDOM_FLOAT( -m_iXJoltForce, m_iXJoltForce ), RANDOM_FLOAT( -m_iYJoltForce, m_iYJoltForce ), RANDOM_FLOAT( -m_iZJoltForce, m_iZJoltForce ) ); ApplyForceToSegment( vecSparkForce, uiSegment ); } } void CElectrifiedWire::DoLightning() { const int uiSegment1 = RANDOM_LONG( 0, GetNumSegments() - 1 ); int uiSegment2; int uiIndex; //Try to get a random segment. for( uiIndex = 0; uiIndex < 10; ++uiIndex ) { uiSegment2 = RANDOM_LONG( 0, GetNumSegments() - 1 ); if( uiSegment2 != uiSegment1 ) break; } if( uiIndex >= 10 ) return; CRopeSegment* pSegment1; CRopeSegment* pSegment2; if( GetToggleValue() ) { pSegment1 = GetAltSegments()[ uiSegment1 ]; pSegment2 = GetAltSegments()[ uiSegment2 ]; } else { pSegment1 = GetSegments()[ uiSegment1 ]; pSegment2 = GetSegments()[ uiSegment2 ]; } MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMENTS ); WRITE_SHORT( pSegment1->entindex() ); WRITE_SHORT( pSegment2->entindex() ); WRITE_SHORT( m_iLightningSprite ); WRITE_BYTE( 0 ); WRITE_BYTE( 0 ); WRITE_BYTE( 1 ); WRITE_BYTE( 10 ); WRITE_BYTE( 80 ); WRITE_BYTE( 255 ); WRITE_BYTE( 255 ); WRITE_BYTE( 255 ); WRITE_BYTE( 255 ); WRITE_BYTE( 255 ); MESSAGE_END(); }