From 6cb150f7f1f2d7376cb0e0b6756685eb92f49a22 Mon Sep 17 00:00:00 2001 From: Roman Chistokhodov Date: Thu, 1 Aug 2019 18:17:29 +0300 Subject: [PATCH] Update ropes --- dlls/CMakeLists.txt | 4 + dlls/gearbox/ropes.cpp | 1833 +++++++++++++++++++++++++++++++++++----- dlls/gearbox/ropes.h | 311 +++++++ dlls/player.cpp | 130 +++ dlls/player.h | 25 + 5 files changed, 2105 insertions(+), 198 deletions(-) create mode 100644 dlls/gearbox/ropes.h diff --git a/dlls/CMakeLists.txt b/dlls/CMakeLists.txt index d4990bfa..1f2d271c 100644 --- a/dlls/CMakeLists.txt +++ b/dlls/CMakeLists.txt @@ -188,6 +188,10 @@ set (SVDLL_SOURCES ../pm_shared/pm_math.c ../pm_shared/pm_shared.c ) + +if(NOT MSVC) + set_source_files_properties(gearbox/ropes.cpp PROPERTIES COMPILE_FLAGS -O0) +endif() include_directories (. wpn_shared ../common ../engine ../pm_shared ../game_shared ../public gearbox) diff --git a/dlls/gearbox/ropes.cpp b/dlls/gearbox/ropes.cpp index 37467e8e..14e411bf 100644 --- a/dlls/gearbox/ropes.cpp +++ b/dlls/gearbox/ropes.cpp @@ -24,322 +24,1759 @@ #include "gamerules.h" #include "effects.h" #include "saverestore.h" +#include "ropes.h" + + +#define SetAbsOrigin(x) pev->origin = x; +#define SetAbsAngles(x) pev->angles = x; +#define SetAbsVelociy(x) pev->velocity = x; +#define SetNextThink(x) pev->nextthink = x; +#define SetEffects(x) pev->effects = x; +#define SetSolidType(x) pev->solid = x; +#define AddFlags(x) pev->flags |= x; +#define SetMoveType(x) pev->movetype = x; +#define AddEffectsFlags(x) pev->effects |= x; + +/** +* Represents a spring that keeps samples a given distance apart. +*/ +struct Spring +{ + size_t p1; + size_t p2; + float restLength; + float hookConstant; + float springDampning; +}; + +/** +* Data for a single rope joint. +*/ +struct RopeSampleData +{ + Vector mPosition; + Vector mVelocity; + Vector mForce; + Vector mExternalForce; + + bool mApplyExternalForce; + + float mMassReciprocal; +}; + +/** +* 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(); + + const RopeSampleData* GetData() const { return &data; } + + RopeSampleData* GetData() { return &data; } + + CRope* GetMasterRope() { return mMasterRope; } + + void SetMasterRope( CRope* pRope ) + { + mMasterRope = pRope; + } + +private: + RopeSampleData data; + CRope* mMasterRope; +}; + + +class CRopeSegment : public CBaseAnimating +{ +public: + + void Precache(); + + void Spawn(); + + void Think(); + + void Touch( CBaseEntity* pOther ); + + static CRopeSegment* CreateSegment( CRopeSample* pSample, string_t iszModelName ); + + CRopeSample* GetSample() { return m_Sample; } + + /** + * Applies external force to the segment. + * @param vecForce Force. + */ + void ApplyExternalForce( const Vector& vecForce ); + + /** + * Resets the mass to the default value. + */ + void SetMassToDefault(); + + /** + * Sets the default mass. + * @param flDefaultMass Mass. + */ + void SetDefaultMass( const float flDefaultMass ); + + /** + * Sets the mass. + * @param flMass Mass. + */ + void SetMass( const float flMass ); + + /** + * Sets whether the segment should cause damage on touch. + * @param bCauseDamage Whether to cause damage. + */ + void SetCauseDamageOnTouch( const bool bCauseDamage ); + + /** + * Sets whether the segment can be grabbed. + * @param bCanBeGrabbed Whether the segment can be grabbed. + */ + void SetCanBeGrabbed( const bool bCanBeGrabbed ); + + 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; +}; + +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_HookConstant, FIELD_FLOAT ), + DEFINE_FIELD( CRope, m_SpringDampning, FIELD_FLOAT ), + DEFINE_FIELD( CRope, m_NumSamples, FIELD_INTEGER ), + DEFINE_FIELD( CRope, m_SpringCnt, 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_CurrentSys, FIELD_CLASSPTR, MAX_SAMPLES ), + DEFINE_ARRAY( CRope, m_TargetSys, 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 ), +}; + +int CRope::Save( CSave &save ) +{ + if( !CBaseDelay::Save( save ) ) + return 0; + return save.WriteFields( "CBaseDelay", this, m_SaveData, ARRAYSIZE( m_SaveData ) ); +} + +int CRope::Restore( CRestore &restore ) +{ + if( !CBaseDelay::Restore( restore ) ) + return 0; + int status = restore.ReadFields( "CBaseDelay", this, m_SaveData, ARRAYSIZE( m_SaveData ) ); + + for( size_t uiIndex = 0; uiIndex < MAX_TEMP_SAMPLES; ++uiIndex ) + { + delete[] m_TempSys[ uiIndex ]; + m_TempSys[ uiIndex ] = new RopeSampleData[ m_NumSamples ]; + + memset( m_TempSys[ uiIndex ], 0, sizeof( RopeSampleData ) * m_NumSamples ); + } + m_InitialDeltaTime = 0; + m_Spring = NULL; + + return status; +} + + +LINK_ENTITY_TO_CLASS( env_rope, CRope ) + +/* leak +CRope::~CRope() +{ + for( size_t uiIndex = 0; uiIndex < MAX_TEMP_SAMPLES; ++uiIndex ) + { + delete[] m_TempSys[ uiIndex ]; + m_TempSys[ uiIndex ] = NULL; + } +}*/ + +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_SOUND_ARRAY( g_pszCreakSounds ); +} + +void CRope::Spawn() +{ + pev->classname = MAKE_STRING( "env_rope" ); + + m_bMakeSound = true; + + Precache(); + + mSpringsInitialized = false; + + m_Gravity.x = m_Gravity.y = 0; + m_Gravity.z = -50; + + mObjectAttached = false; + + AddFlags( FL_ALWAYSTHINK ); + m_NumSamples = m_iSegments + 1; + + for( size_t uiSample = 0; uiSample < m_NumSamples; ++uiSample ) + { + m_CurrentSys[ uiSample ] = CRopeSample::CreateSample(); + + m_CurrentSys[ uiSample ]->SetMasterRope( this ); + } + + memset( m_CurrentSys + m_NumSamples, 0, sizeof( CRopeSample* ) * ( MAX_SAMPLES - m_NumSamples ) ); + + { + CRopeSegment* pSegment = seg[ 0 ] = CRopeSegment::CreateSegment( m_CurrentSys[ 0 ], GetBodyModel() ); + + pSegment->SetAbsOrigin( pev->origin ); + + pSegment = altseg[ 0 ] = CRopeSegment::CreateSegment( m_CurrentSys[ 0 ], GetBodyModel() ); + + pSegment->SetAbsOrigin( pev->origin ); + } + + Vector origin; + Vector angles; + + const Vector vecGravity = m_Gravity.Normalize(); + + if( m_iSegments > 2 ) + { + CRopeSample** ppCurrentSys = m_CurrentSys; + + for( size_t uiSeg = 1; uiSeg < m_iSegments - 1; ++uiSeg ) + { + CRopeSample* pSegSample = m_CurrentSys[ uiSeg ]; + seg[ uiSeg ] = CRopeSegment::CreateSegment( pSegSample, GetBodyModel() ); + + altseg[ uiSeg ] = CRopeSegment::CreateSegment( pSegSample, GetBodyModel() ); + + 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_CurrentSys[ m_iSegments - 1 ]; + seg[ m_iSegments - 1 ] = CRopeSegment::CreateSegment( pSegSample, GetEndingModel() ); + + altseg[ m_iSegments - 1 ] = CRopeSegment::CreateSegment( pSegSample, GetEndingModel() ); + + 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 ) ); + + memset( m_TempSys, 0, sizeof( m_TempSys ) ); + + m_SpringCnt = 0; + + m_InitialDeltaTime = true; + m_HookConstant = 2500; + m_SpringDampning = 0.1; + + InitializeRopeSim(); + + SetNextThink( gpGlobals->time + 0.01 ); +} + +void CRope::Think() +{ + if( !mSpringsInitialized ) + { + InitializeSprings( m_iSegments ); + } + + 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(); + } + + SetNextThink( gpGlobals->time + 0.001 ); +} + +void CRope::Touch( CBaseEntity* pOther ) +{ + //Nothing. +} + +void CRope::InitializeRopeSim() +{ + size_t uiIndex; + for( uiIndex = 0; uiIndex < MAX_TEMP_SAMPLES; ++uiIndex ) + { + delete[] m_TempSys[ uiIndex ]; + m_TempSys[ uiIndex ] = NULL; + } + + for( size_t uiSample = 0; uiSample < m_NumSamples; ++uiSample ) + { + m_TargetSys[ uiSample ] = CRopeSample::CreateSample(); + + m_TargetSys[ uiSample ]->SetMasterRope( this ); + } + + memset( m_TargetSys + m_NumSamples, 0, sizeof( CRopeSample* ) * ( MAX_SAMPLES - m_NumSamples ) ); + + for( uiIndex = 0; uiIndex < MAX_TEMP_SAMPLES; ++uiIndex ) + { + m_TempSys[ uiIndex ] = new RopeSampleData[ m_NumSamples ]; + + memset( m_TempSys[ uiIndex ], 0, sizeof( RopeSampleData ) * m_NumSamples ); + } + + for( size_t 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; + + pSegment->SetDefaultMass( data->mMassReciprocal ); + } + + { + //Zero out the anchored segment's mass so it stays in place. + CRopeSample *pSample = m_CurrentSys[ 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_CurrentSys[ 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; + + size_t uiNumSegs = 4; + + if( m_iSegments <= 4 ) + uiNumSegs = m_iSegments; + + for( uiIndex = 0; uiIndex < uiNumSegs; ++uiIndex ) + { + seg[ uiIndex ]->SetCanBeGrabbed( false ); + altseg[ uiIndex ]->SetCanBeGrabbed( false ); + } +} + +void CRope::InitializeSprings( const size_t uiNumSprings ) +{ + m_SpringCnt = uiNumSprings; + + m_Spring = new Spring[ uiNumSprings ]; + + if( uiNumSprings > 0 ) + { + Vector vecOrigin, vecAngles; + + for( size_t uiIndex = 0; uiIndex < m_SpringCnt; ++uiIndex ) + { + Spring& spring = m_Spring[ uiIndex ]; + + spring.p1 = uiIndex; + spring.p2 = uiIndex + 1; + + if( uiIndex < m_iSegments ) + { + CRopeSegment* pSegment = seg[ uiIndex ]; + + pSegment->GetAttachment( 0, vecOrigin, vecAngles ); + + spring.restLength = ( pSegment->pev->origin - vecOrigin ).Length(); + } + else + spring.restLength = 0; + + spring.hookConstant = m_HookConstant; + spring.springDampning = m_SpringDampning; + } + } + + mSpringsInitialized = true; +} + +void CRope::RunSimOnSamples() +{ + float flDeltaTime = 0.025; + + if( m_InitialDeltaTime ) + { + m_InitialDeltaTime = false; + mLastTime = gpGlobals->time; + flDeltaTime = 0; + } + + size_t uiIndex = 0; + + CRopeSample** ppSampleSource = m_CurrentSys; + CRopeSample** ppSampleTarget = m_TargetSys; + + while( true ) + { + ++uiIndex; + + ComputeForces( ppSampleSource ); + RK4Integrate( flDeltaTime, ppSampleSource, ppSampleTarget ); + + mLastTime += 0.007; + + if( gpGlobals->time <= mLastTime ) + { + if( ( uiIndex % 2 ) != 0 ) + break; + } + CRopeSample **swap = ppSampleSource; + ppSampleSource = ppSampleTarget; + ppSampleTarget = swap; + + //std::swap( ppSampleSource, ppSampleTarget ); + + } + + mLastTime = gpGlobals->time; +} + +void CRope::ComputeForces( RopeSampleData* pSystem ) +{ + size_t uiIndex; + for( uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex ) + { + ComputeSampleForce( pSystem[ uiIndex ] ); + } + + Spring* pSpring = m_Spring; + + for( uiIndex = 0; uiIndex < m_SpringCnt; ++uiIndex, ++pSpring ) + { + ComputeSpringForce( pSystem[ pSpring->p1 ], pSystem[ pSpring->p2 ], *pSpring ); + } +} + +void CRope::ComputeForces( CRopeSample** ppSystem ) +{ + size_t uiIndex; + for( uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex ) + { + ComputeSampleForce( *ppSystem[ uiIndex ]->GetData() ); + } + + Spring* pSpring = m_Spring; + + for( uiIndex = 0; uiIndex < m_SpringCnt; ++uiIndex, ++pSpring ) + { + ComputeSpringForce( *ppSystem[ pSpring->p1 ]->GetData(), *ppSystem[ pSpring->p2 ]->GetData(), *pSpring ); + } +} + +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, const Spring& spring ) +{ + Vector vecDist = first.mPosition - second.mPosition; + + const double flDistance = vecDist.Length(); + + const double flForce = ( flDistance - spring.restLength ) * spring.hookConstant; + + const double flNewRelativeDist = DotProduct( first.mVelocity - second.mVelocity, vecDist ) * spring.springDampning; + + 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, CRopeSample** ppSampleSource, CRopeSample** ppSampleTarget ) +{ + const float flDeltas[ MAX_TEMP_SAMPLES - 1 ] = + { + flDeltaTime * 0.5f, + flDeltaTime * 0.5f, + flDeltaTime * 0.5f, + flDeltaTime + }; + + { + RopeSampleData* pTemp1 = m_TempSys[ 0 ]; + RopeSampleData* pTemp2 = m_TempSys[ 1 ]; + + for( size_t uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex, ++pTemp1, ++pTemp2 ) + { + RopeSampleData *data = ppSampleSource[ uiIndex ]->GetData(); + + pTemp2->mForce = data->mMassReciprocal * data->mForce * flDeltas[ 0 ]; + + pTemp2->mVelocity = data->mVelocity * flDeltas[ 0 ]; + + pTemp1->mMassReciprocal = data->mMassReciprocal; + pTemp1->mVelocity = data->mVelocity + pTemp2->mForce; + pTemp1->mPosition = data->mPosition + pTemp2->mVelocity; + } + + ComputeForces( m_TempSys[ 0 ] ); + } + + for( size_t uiStep = 2; uiStep < MAX_TEMP_SAMPLES - 1; ++uiStep ) + { + RopeSampleData* pTemp1 = m_TempSys[ 0 ]; + RopeSampleData* pTemp2 = m_TempSys[ uiStep ]; + + for( size_t uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex, ++pTemp1, ++pTemp2 ) + { + RopeSampleData *data = ppSampleSource[ uiIndex ]->GetData(); + + pTemp2->mForce = data->mMassReciprocal * pTemp1->mForce * flDeltas[ uiStep - 1 ]; + + pTemp2->mVelocity = pTemp1->mVelocity * flDeltas[ uiStep - 1 ]; + + pTemp1->mMassReciprocal = data->mMassReciprocal; + pTemp1->mVelocity = data->mVelocity + pTemp2->mForce; + pTemp1->mPosition = data->mPosition + pTemp2->mVelocity; + } + + ComputeForces( m_TempSys[ 0 ] ); + } + + { + RopeSampleData* pTemp1 = m_TempSys[ 0 ]; + RopeSampleData* pTemp2 = m_TempSys[ 4 ]; + + for( size_t uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex, ++pTemp1, ++pTemp2 ) + { + RopeSampleData *data = ppSampleSource[ uiIndex ]->GetData(); + + pTemp2->mForce = data->mMassReciprocal * pTemp1->mForce * flDeltas[ 3 ]; + + pTemp2->mVelocity = pTemp1->mVelocity * flDeltas[ 3 ]; + } + } + + RopeSampleData* pTemp1 = m_TempSys[ 1 ]; + RopeSampleData* pTemp2 = m_TempSys[ 2 ]; + RopeSampleData* pTemp3 = m_TempSys[ 3 ]; + RopeSampleData* pTemp4 = m_TempSys[ 4 ]; + + for( size_t uiIndex = 0; uiIndex < m_NumSamples; ++uiIndex, ++pTemp1, ++pTemp2, ++pTemp3, ++pTemp4 ) + { + CRopeSample *pSource = ppSampleSource[ uiIndex ]; + CRopeSample *pTarget = ppSampleTarget[ uiIndex ]; + + 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->GetData()->mPosition = pSource->GetData()->mPosition + ( vecPosChange );//* flDeltaTime ); + + pTarget->GetData()->mVelocity = pSource->GetData()->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_CurrentSys[ 0 ]->GetData()->mPosition, + m_CurrentSys[ 1 ]->GetData()->mPosition, + vecAngles ); + + ( *ppPrimarySegs )->SetAbsAngles( vecAngles ); + } + + TraceResult tr; + + if( mObjectAttached ) + { + for( size_t uiSeg = 1; uiSeg < m_iSegments; ++uiSeg ) + { + CRopeSample* pSample = m_CurrentSys[ uiSeg ]; + + Vector vecDist = pSample->GetData()->mPosition - ppHiddenSegs[ uiSeg ]->pev->origin; + + vecDist = vecDist.Normalize(); + + 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( size_t uiSeg = 1; uiSeg < m_iSegments; ++uiSeg ) + { + UTIL_TraceLine( + ppHiddenSegs[ uiSeg ]->pev->origin, + m_CurrentSys[ uiSeg ]->GetData()->mPosition, + ignore_monsters, edict(), &tr ); + + if( tr.flFraction == 1.0 ) + { + Vector vecOrigin = m_CurrentSys[ 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( size_t uiSeg = 1; uiSeg < m_iSegments; ++uiSeg ) + { + CRopeSegment *pSegment = ppPrimarySegs[ uiSeg - 1 ]; + CRopeSegment *pSegment2 = ppPrimarySegs[ uiSeg ]; + + GetAlignmentAngles( pSegment->pev->origin, pSegment2->pev->origin, vecAngles ); + + pSegment->SetAbsAngles( vecAngles ); + } + + if( m_iSegments > 1 ) + { + CRopeSample *pSample = m_CurrentSys[ 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->SetAbsAngles( vecAngles ); + } +} + +void CRope::SetRopeSegments( const size_t uiNumSegments, + CRopeSegment** ppPrimarySegs, CRopeSegment** ppHiddenSegs ) +{ + if( uiNumSegments > 0 ) + { + TraceModels( ppPrimarySegs, ppHiddenSegs ); + + ppPrimarySegs[ 0 ]->SetSolidType( SOLID_TRIGGER ); + ppPrimarySegs[ 0 ]->SetEffects( 0 ); + + ppHiddenSegs[ 0 ]->SetSolidType( SOLID_NOT ); + ppHiddenSegs[ 0 ]->SetEffects( EF_NODRAW ); + + for( size_t uiIndex = 1; uiIndex < uiNumSegments; ++uiIndex ) + { + CRopeSegment* pPrim = ppPrimarySegs[ uiIndex ]; + CRopeSegment* pHidden = ppHiddenSegs[ uiIndex ]; + + pPrim->SetSolidType( SOLID_TRIGGER ); + pPrim->SetEffects( 0 ); + + pHidden->SetSolidType( SOLID_NOT ); + pHidden->SetEffects( 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; + } -#define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) ) + if( mAttachedObjectsSegment + 1 == m_iSegments ) + bOnRope = false; + else + ++mAttachedObjectsSegment; -class CRopeSegment : public CBaseEntity + flNewDist -= flOffset; + flSegLength = 0; + + mAttachedObjectsOffset = 0; + + if( flNewDist <= 0 ) + break; + } + } + } + + return bOnRope; +} + +Vector CRope::GetAttachedObjectsVelocity() const { -public: - int Save(CSave &save); - int Restore(CRestore &restore); - static TYPEDESCRIPTION m_SaveData[]; + if( !mObjectAttached ) + return g_vecZero; + + return seg[ mAttachedObjectsSegment ]->GetSample()->GetData()->mVelocity; +} - void Spawn(void); +void CRope::ApplyForceFromPlayer( const Vector& vecForce ) +{ + if( !mObjectAttached ) + return; - void EXPORT SegmentThink(void); - void EXPORT SegmentTouch(CBaseEntity* pOther); + float flForce = 20000.0; - CRopeSegment* m_pNext; - CRopeSegment* m_pPrev; + if( m_iSegments < 26 ) + flForce *= ( m_iSegments / 26.0 ); - Vector m_vecJointPos; -}; + const Vector vecScaledForce = vecForce * flForce; -LINK_ENTITY_TO_CLASS(rope_segment, CRopeSegment); + ApplyForceToSegment( vecScaledForce, mAttachedObjectsSegment ); +} -TYPEDESCRIPTION CRopeSegment::m_SaveData[] = +void CRope::ApplyForceToSegment( const Vector& vecForce, const size_t uiSegment ) { - DEFINE_FIELD(CRopeSegment, m_pNext, FIELD_CLASSPTR), - DEFINE_FIELD(CRopeSegment, m_pPrev, FIELD_CLASSPTR), - DEFINE_FIELD(CRopeSegment, m_vecJointPos, FIELD_POSITION_VECTOR), -}; + if( uiSegment < m_iSegments ) + { + seg[ uiSegment ]->ApplyExternalForce( vecForce ); + } + else if( uiSegment == m_iSegments ) + { + //Apply force to the last sample. -IMPLEMENT_SAVERESTORE(CRopeSegment, CBaseEntity); + RopeSampleData *data = m_CurrentSys[ uiSegment - 1 ]->GetData(); + data->mExternalForce = data->mExternalForce + vecForce; -void CRopeSegment::Spawn(void) + data->mApplyExternalForce = true; + } +} + +void CRope::AttachObjectToSegment( CRopeSegment* pSegment ) { - SET_MODEL(ENT(pev), "models/rope16.mdl"); + mObjectAttached = true; - pev->solid = SOLID_NOT; - pev->movetype = MOVETYPE_NONE; - pev->gravity = 0.0f; + detachTime = 0; - m_pPrev = NULL; - m_pNext = NULL; - m_vecJointPos = Vector(0, 0, 0); + SetAttachedObjectsSegment( pSegment ); - SetTouch(&CRopeSegment::Touch); - SetThink(&CRopeSegment::SegmentThink); - pev->nextthink = gpGlobals->time + 0.1f; + mAttachedObjectsOffset = 0; } -void CRopeSegment::SegmentThink(void) +void CRope::DetachObject() { - pev->nextthink = gpGlobals->time + 0.1f; + mObjectAttached = false; + detachTime = gpGlobals->time; } -void CRopeSegment::SegmentTouch(CBaseEntity* pOther) +bool CRope::IsAcceptingAttachment() const { - ALERT(at_console, "Touched segment!\n"); + if( gpGlobals->time - detachTime > 2.0 && !mObjectAttached ) + { + return mDisallowPlayerAttachment != 1; + } - SetTouch(NULL); + 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; +} -#define MAX_ROPE_SEGMENTS 64 +void CRope::Creak() +{ + EMIT_SOUND( edict(), CHAN_BODY, + g_pszCreakSounds[ RANDOM_LONG( 0, ARRAYSIZE( g_pszCreakSounds ) - 1 ) ], + VOL_NORM, ATTN_NORM ); +} -class CRope : public CBaseEntity +float CRope::GetSegmentLength( size_t uiSegmentIndex ) const { -public: + if( uiSegmentIndex < m_iSegments ) + { + Vector vecOrigin, vecAngles; - int Save(CSave &save); - int Restore(CRestore &restore); - static TYPEDESCRIPTION m_SaveData[]; + CRopeSegment *pSegment = seg[ uiSegmentIndex ]; - void KeyValue(KeyValueData* pkvd); - void Spawn(void); - void Precache(void); + pSegment->GetAttachment( 0, vecOrigin, vecAngles ); - void EXPORT StartupThink(void); - void EXPORT RopeThink(void); + return ( pSegment->pev->origin - vecOrigin ).Length(); + } - void CreateSegments(); - void DestroySegments(); + return 0; +} - int FindClosestSegment(Vector& vecTo, float epsilon, int iMin, int iMax); +float CRope::GetRopeLength() const +{ + float flLength = 0; - CRopeSegment* m_pSegments[MAX_ROPE_SEGMENTS]; + Vector vecOrigin, vecAngles; - int m_nSegments; - BOOL m_fEnabled; + for( size_t uiIndex = 0; uiIndex < m_iSegments; ++uiIndex ) + { + CRopeSegment *pSegment = seg[ uiIndex ]; - string_t m_iszBodyModel; - string_t m_iszEndingModel; + pSegment->GetAttachment( 0, vecOrigin, vecAngles ); - void ApplyFunctor(void(*functor)(CRopeSegment* pSegment), int startIndex, int endIndex); - void ApplyFunctor(void(*functor)(CRopeSegment* pSegment)); -}; + flLength += ( pSegment->pev->origin - vecOrigin ).Length(); + } -LINK_ENTITY_TO_CLASS(env_rope, CRope); + return flLength; +} -TYPEDESCRIPTION CRope::m_SaveData[] = +Vector CRope::GetRopeOrigin() const { - DEFINE_ARRAY(CRope, m_pSegments, FIELD_CLASSPTR, MAX_ROPE_SEGMENTS), - DEFINE_FIELD(CRope, m_nSegments, FIELD_INTEGER), - DEFINE_FIELD(CRope, m_fEnabled, FIELD_BOOLEAN), - DEFINE_FIELD(CRope, m_iszBodyModel, FIELD_STRING), - DEFINE_FIELD(CRope, m_iszEndingModel, FIELD_STRING), -}; + return m_CurrentSys[ 0 ]->GetData()->mPosition; +} -IMPLEMENT_SAVERESTORE(CRope, CBaseEntity); +bool CRope::IsValidSegmentIndex( const size_t uiSegment ) const +{ + return uiSegment < m_iSegments; +} -void CRope::KeyValue(KeyValueData* pkvd) +Vector CRope::GetSegmentOrigin( const size_t uiSegment ) const { - if (FStrEq(pkvd->szKeyName, "segments")) - { - m_nSegments = atoi(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "disable")) - { - m_fEnabled = !atoi(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "endingmodel")) - { - m_iszEndingModel = ALLOC_STRING(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "bodymodel")) + if( !IsValidSegmentIndex( uiSegment ) ) + return g_vecZero; + + return m_CurrentSys[ uiSegment ]->GetData()->mPosition; +} + +Vector CRope::GetSegmentAttachmentPoint( const size_t 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( size_t uiIndex = 0; uiIndex < m_iSegments; ++uiIndex ) { - m_iszBodyModel = ALLOC_STRING(pkvd->szValue); - pkvd->fHandled = TRUE; + if( seg[ uiIndex ] == pSegment || altseg[ uiIndex ] == pSegment ) + { + mAttachedObjectsSegment = uiIndex; + break; + } } - else - CBaseEntity::KeyValue(pkvd); } -void CRope::Spawn(void) +Vector CRope::GetSegmentDirFromOrigin( const size_t uiSegmentIndex ) const { - Precache(); + if( uiSegmentIndex >= m_iSegments ) + return g_vecZero; + + //There is one more sample than there are segments, so this is fine. + const Vector vecResult = + m_CurrentSys[ uiSegmentIndex + 1 ]->GetData()->mPosition - + m_CurrentSys[ uiSegmentIndex ]->GetData()->mPosition; + + return vecResult.Normalize(); +} + +Vector CRope::GetAttachedObjectsPosition() const +{ + if( !mObjectAttached ) + return g_vecZero; + + Vector vecResult; - pev->solid = SOLID_NOT; - pev->movetype = MOVETYPE_NONE; - pev->effects = 0; + if( mAttachedObjectsSegment < m_iSegments ) + vecResult = m_CurrentSys[ mAttachedObjectsSegment ]->GetData()->mPosition; - for (int i = 0; i < MAX_ROPE_SEGMENTS; i++) - m_pSegments[i] = NULL; + vecResult = vecResult + + ( mAttachedObjectsOffset * GetSegmentDirFromOrigin( mAttachedObjectsSegment ) ); - SetThink(&CRope::StartupThink); - pev->nextthink = gpGlobals->time + 0.1; + return vecResult; } -void CRope::Precache(void) + + +// 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, mMasterRope, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE(CRopeSample, CBaseEntity) - PRECACHE_MODEL("models/rope16.mdl"); - PRECACHE_MODEL("models/rope24.mdl"); - PRECACHE_MODEL("models/rope32.mdl"); +LINK_ENTITY_TO_CLASS( rope_sample, CRopeSample ); - UTIL_PrecacheOther("rope_segment"); +void CRopeSample::Spawn() +{ + pev->classname = MAKE_STRING( "rope_sample" ); + + AddEffectsFlags( EF_NODRAW ); } -void CRope::StartupThink(void) +CRopeSample* CRopeSample::CreateSample() { - pev->nextthink = gpGlobals->time + 0.1; + CRopeSample* pSample = GetClassPtr( NULL ); - CreateSegments(); + pSample->Spawn(); - SetThink(&CRope::RopeThink); + return pSample; } -//=============================================== -// Use binary search for closest point. -//=============================================== -int CRope::FindClosestSegment(Vector& vecTo, float epsilon, int iMin, int iMax) + +TYPEDESCRIPTION CRopeSegment::m_SaveData[] = { - if (iMax < iMin) - return -1; - else + 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 ), +}; +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( (char*)STRING( mModelName ) ); + PRECACHE_SOUND( "items/grab_rope.wav" ); +} + +void CRopeSegment::Spawn() +{ + pev->classname = MAKE_STRING( "rope_segment" ); + + Precache(); + + SET_MODEL( edict(), STRING( mModelName ) ); + + SetMoveType( MOVETYPE_NOCLIP ); + SetSolidType( SOLID_TRIGGER ); + AddFlags( FL_ALWAYSTHINK ); + SetEffects( EF_NODRAW ); + SetAbsOrigin( pev->origin ); + + UTIL_SetSize( pev, Vector( -30, -30, -30 ), Vector( 30, 30, 30 ) ); + + SetNextThink( gpGlobals->time + 0.5 ); +} + +void CRopeSegment::Think() +{ + //Do nothing. +} + +void CRopeSegment::Touch( CBaseEntity* pOther ) +{ + if( pOther->IsPlayer() ) { - int middle = iMin + ((iMax - iMin) / 2); + CBasePlayer *pPlayer = static_cast( pOther ); - if (vecTo.z > (m_pSegments[middle]->pev->origin.z + epsilon)) + //Electrified wires deal damage. - Solokiller + if( mCauseDamage ) { - return FindClosestSegment(vecTo, epsilon, iMin, middle - 1); // Higher ropes are at vector head. + pOther->TakeDamage( pev, pev, 1, DMG_SHOCK ); } - else if (vecTo.z < (m_pSegments[middle]->pev->origin.z - epsilon)) - { - return FindClosestSegment(vecTo, epsilon, middle + 1, iMax); // Lower ropes are at vector tail. - } - else + + if( m_Sample->GetMasterRope()->IsAcceptingAttachment() && !(pPlayer->m_afPhysicsFlags & PFLAG_ONROPE) ) { - return middle; + if( mCanBeGrabbed ) + { + RopeSampleData *data = m_Sample->GetData(); + + pOther->SetAbsOrigin( data->mPosition ); + + pPlayer->SetOnRopeState( true ); + pPlayer->SetRope( m_Sample->GetMasterRope() ); + m_Sample->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( m_Sample->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 = m_Sample->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 ); + } } } } -static void Functor_SetSegmentFxGlow(CRopeSegment* pSegment) +CRopeSegment* CRopeSegment::CreateSegment( CRopeSample* pSample, string_t iszModelName ) +{ + CRopeSegment* pSegment = GetClassPtr( NULL ); + + pSegment->mModelName = iszModelName; + + pSegment->Spawn(); + + pSegment->m_Sample = pSample; + + pSegment->mCauseDamage = false; + pSegment->mCanBeGrabbed = true; + pSegment->mDefaultMass = pSample->GetData()->mMassReciprocal; + + return pSegment; +} + +void CRopeSegment::ApplyExternalForce( const Vector& vecForce ) +{ + m_Sample->GetData()->mApplyExternalForce = true; + + m_Sample->GetData()->mExternalForce = m_Sample->GetData()->mExternalForce + vecForce; +} + +void CRopeSegment::SetMassToDefault() +{ + m_Sample->GetData()->mMassReciprocal = mDefaultMass; +} + +void CRopeSegment::SetDefaultMass( const float flDefaultMass ) +{ + mDefaultMass = flDefaultMass; +} + +void CRopeSegment::SetMass( const float flMass ) +{ + m_Sample->GetData()->mMassReciprocal = flMass; +} + +void CRopeSegment::SetCauseDamageOnTouch( const bool bCauseDamage ) +{ + mCauseDamage = bCauseDamage; +} + +void CRopeSegment::SetCanBeGrabbed( const bool bCanBeGrabbed ) { - pSegment->pev->renderfx = kRenderFxGlowShell; + mCanBeGrabbed = bCanBeGrabbed; } -static void Functor_SetSegmentFxNormal(CRopeSegment* pSegment) + +class CElectrifiedWire : public CRope +{ +public: + CElectrifiedWire(); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void KeyValue( KeyValueData* pkvd ); + + void Precache(); + + void Spawn(); + + void Think(); + + 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 size_t 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; + + size_t m_uiNumUninsulatedSegments; + size_t m_uiUninsulatedSegments[ MAX_SEGMENTS ]; + + int m_iLightningSprite; + + float m_flLastSparkTime; +}; + +CElectrifiedWire::CElectrifiedWire() { - pSegment->pev->renderfx = kRenderFxNone; + m_bIsActive = true; + m_iTipSparkFrequency = 3; + m_iBodySparkFrequency = 100; + m_iLightningFrequency = 150; + m_iXJoltForce = 0; + m_iYJoltForce = 0; + m_iZJoltForce = 0; + m_uiNumUninsulatedSegments = 0; } -void CRope::RopeThink(void) + +TYPEDESCRIPTION CElectrifiedWire::m_SaveData[] = { - pev->nextthink = gpGlobals->time + 0.1; + DEFINE_FIELD( CElectrifiedWire, m_bIsActive, FIELD_CHARACTER ), - CBaseEntity* pEntity = UTIL_PlayerByIndex(1); - if (pEntity) + 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" ) ) { - if ((pEntity->pev->origin - pev->origin).Length() > 384) - return; + m_iTipSparkFrequency = strtol( pkvd->szValue, NULL, 10 ); - ApplyFunctor(Functor_SetSegmentFxNormal); + pkvd->fHandled = true; + } + else if( FStrEq( pkvd->szKeyName, "bodysparkfrequency" ) ) + { + m_iBodySparkFrequency = strtol( pkvd->szValue, NULL, 10 ); - Vector v = ((CBasePlayer*)pEntity)->GetGunPosition(); - int index = FindClosestSegment(v, 16, 0, m_nSegments - 1); - if (index >= 0 && index < m_nSegments) - { - CRopeSegment* pClosestSegment = m_pSegments[index]; + pkvd->fHandled = true; + } + else if( FStrEq( pkvd->szKeyName, "lightningfrequency" ) ) + { + m_iLightningFrequency = strtol( pkvd->szValue, NULL, 10 ); - if (pClosestSegment) - { - pClosestSegment->SetTouch(&CRopeSegment::SegmentTouch); - pClosestSegment->pev->renderfx = kRenderFxGlowShell; - pClosestSegment->pev->rendercolor = Vector(255, 0, 0); - ALERT(at_console, "Rope joint closest is %d at %f\n", index, pClosestSegment->pev->origin.z); - } - } + 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" ); } -//=============================================== -// Create all rope segments. -//=============================================== -void CRope::CreateSegments() +void CElectrifiedWire::Spawn() { - CRopeSegment* pRopeSegment = NULL; - Vector down = Vector(0, 0, -1); - Vector angles = Vector(0, 0, -90); + CRope::Spawn(); + pev->classname = MAKE_STRING( "env_electrified_wire" ); + + m_uiNumUninsulatedSegments = 0; + m_bIsActive = true; - Vector joint; + if( m_iBodySparkFrequency > 0 ) + { + for( size_t uiIndex = 0; uiIndex < GetNumSegments(); ++uiIndex ) + { + if( IsValidSegmentIndex( uiIndex ) ) + { + m_uiUninsulatedSegments[ m_uiNumUninsulatedSegments++ ] = uiIndex; + } + } + } - joint = pev->origin + (down * 16); + if( m_uiNumUninsulatedSegments > 0 ) + { + for( size_t uiIndex = 0; uiIndex < m_uiNumUninsulatedSegments; ++uiIndex ) + { + GetSegments()[ uiIndex ]->SetCauseDamageOnTouch( m_bIsActive ); + GetAltSegments()[ uiIndex ]->SetCauseDamageOnTouch( m_bIsActive ); + } + } - m_pSegments[0] = (CRopeSegment*)CBaseEntity::Create( - "rope_segment", - joint, - angles); + if( m_iTipSparkFrequency > 0 ) + { + GetSegments()[ GetNumSegments() - 1 ]->SetCauseDamageOnTouch( m_bIsActive ); + GetAltSegments()[ GetNumSegments() - 1 ]->SetCauseDamageOnTouch( m_bIsActive ); + } - m_pSegments[0]->m_vecJointPos = joint; + m_flLastSparkTime = gpGlobals->time; - m_pSegments[0]->m_pPrev = NULL; + SetSoundAllowed( false ); +} - int i; - for (i = 1; i < m_nSegments; i++) +void CElectrifiedWire::Think() +{ + if( gpGlobals->time - m_flLastSparkTime > 0.1 ) { - joint = pev->origin + (down * 16 * i); + m_flLastSparkTime = gpGlobals->time; - m_pSegments[i] = (CRopeSegment*)CBaseEntity::Create("rope_segment", joint, angles); + if( m_uiNumUninsulatedSegments > 0 ) + { + for( size_t uiIndex = 0; uiIndex < m_uiNumUninsulatedSegments; ++uiIndex ) + { + if( ShouldDoEffect( m_iBodySparkFrequency ) ) + { + DoSpark( m_uiUninsulatedSegments[ uiIndex ], false ); + } + } + } - m_pSegments[i]->m_vecJointPos = joint; + if( ShouldDoEffect( m_iTipSparkFrequency ) ) + { + DoSpark( GetNumSegments() - 1, true ); + } - m_pSegments[i - 1]->m_pNext = m_pSegments[i]; - m_pSegments[i]->m_pPrev = m_pSegments[i - 1]; - m_pSegments[i]->m_pNext = NULL; + if( ShouldDoEffect( m_iLightningFrequency ) ) + DoLightning(); } + + CRope::Think(); } -//=============================================== -// Destroy all rope segments -//=============================================== -void CRope::DestroySegments(void) +void CElectrifiedWire::Use( CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float flValue ) { - int i; - for (i = 0; i < m_nSegments; i++) + m_bIsActive = !m_bIsActive; + + if( m_uiNumUninsulatedSegments > 0 ) { - if (m_pSegments[i]) + for( size_t uiIndex = 0; uiIndex < m_uiNumUninsulatedSegments; ++uiIndex ) { - UTIL_Remove(m_pSegments[i]); - m_pSegments[i] = NULL; + 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 ); + } } -//=============================================== -// Apply a functor to a range of segments. -//=============================================== -void CRope::ApplyFunctor(void(*functor)(CRopeSegment* pSegment), int startIndex, int endIndex) +bool CElectrifiedWire::ShouldDoEffect( const int iFrequency ) { - // No functor? Return. - if (!functor) - return; + if( iFrequency <= 0 ) + return false; + + if( !IsActive() ) + return false; + + return RANDOM_LONG( 1, iFrequency ) == 1; +} + +void CElectrifiedWire::DoSpark( const size_t uiSegment, const bool bExertForce ) +{ + const Vector vecOrigin = GetSegmentAttachmentPoint( uiSegment ); - // To pass -1 as value for endindex means to apply the functor to all segments. - int endindex = (endIndex != -1) ? endIndex : (m_nSegments - 1); + UTIL_Sparks( vecOrigin ); - // Apply functor to all segments. - int i; - for (i = 0; i <= endindex; i++) + if( bExertForce ) { - // Apply functor. - if (m_pSegments[i]) - functor(m_pSegments[i]); + const Vector vecSparkForce( + RANDOM_FLOAT( -m_iXJoltForce, m_iXJoltForce ), + RANDOM_FLOAT( -m_iYJoltForce, m_iYJoltForce ), + RANDOM_FLOAT( -m_iZJoltForce, m_iZJoltForce ) + ); + + ApplyForceToSegment( vecSparkForce, uiSegment ); } } -//=============================================== -// Apply a functor to all segments. -//=============================================== -void CRope::ApplyFunctor(void(*functor)(CRopeSegment* pSegment)) +void CElectrifiedWire::DoLightning() { - ApplyFunctor(functor, 0, -1); + const size_t uiSegment1 = RANDOM_LONG( 0, GetNumSegments() - 1 ); + + size_t uiSegment2; + + size_t 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(); } diff --git a/dlls/gearbox/ropes.h b/dlls/gearbox/ropes.h new file mode 100644 index 00000000..8e8cc90d --- /dev/null +++ b/dlls/gearbox/ropes.h @@ -0,0 +1,311 @@ +/*** +* +* Copyright (c) 1996-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. +* +****/ +#ifndef ROPES_H +#define ROPES_H + +class CRopeSegment; +class CRopeSample; + +struct RopeSampleData; + +struct Spring; + +#define MAX_SEGMENTS 63 +#define MAX_SAMPLES 64 +#define MAX_TEMP_SAMPLES 5 + +/** +* A rope with a number of segments. +* Uses an RK4 integrator with dampened springs to simulate rope physics. +*/ +class CRope : public CBaseDelay +{ +public: + CRope(); + virtual void KeyValue( KeyValueData* pkvd ); + + virtual void Precache(); + + virtual void Spawn(); + + void Think(); + + void Touch( CBaseEntity* pOther ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + /** + * Initializes the rope simulation data. + */ + void InitializeRopeSim(); + + /** + * Initializes the springs. + * @param uiNumSprings Number of springs to create. + */ + void InitializeSprings( const size_t uiNumSprings ); + + /** + * Runs simulation on the samples. + */ + void RunSimOnSamples(); + + /** + * Computes forces on the given sample list. + * @param pSystem List of sample data. m_NumSamples Elements large. + */ + void ComputeForces( RopeSampleData* pSystem ); + + /** + * Computes forces on the given sample list. + * @param pSystem List of samples. m_NumSamples Elements large. + */ + void ComputeForces( CRopeSample** ppSystem ); + + /** + * Computes forces for the given sample data. + * @param data Sample data. + */ + void ComputeSampleForce( RopeSampleData& data ); + + /** + * Computes spring force for the given sample datas. + * @param first First sample. + * @param second Second sample. + * @param spring Spring. + */ + void ComputeSpringForce( RopeSampleData& first, RopeSampleData& second, const Spring& spring ); + + /** + * Runs RK4 integration. + * @param flDeltaTime Delta between previous and current time. + * @param ppSampleSource Previous sample state. + * @param ppSampleTarget Next sample state. + */ + void RK4Integrate( const float flDeltaTime, CRopeSample** ppSampleSource, CRopeSample** ppSampleTarget ); + + /** + * Traces model positions and angles and corrects them. + * @param ppPrimarySegs Visible segments. + * @param ppHiddenSegs hidden segments. + */ + void TraceModels( CRopeSegment** ppPrimarySegs, CRopeSegment** ppHiddenSegs ); + + /** + * Traces model positions and angles, makes visible segments visible and hidden segments hidden. + */ + void SetRopeSegments( const size_t uiNumSegments, + CRopeSegment** ppPrimarySegs, CRopeSegment** ppHiddenSegs ); + + /** + * Moves the attached object up. + * @param flDeltaTime Time between previous and current movement. + * @return true if the object is still on the rope, false otherwise. + */ + bool MoveUp( const float flDeltaTime ); + + /** + * Moves the attached object down. + * @param flDeltaTime Time between previous and current movement. + * @return true if the object is still on the rope, false otherwise. + */ + bool MoveDown( const float flDeltaTime ); + + /** + * @return The attached object's velocity. + */ + Vector GetAttachedObjectsVelocity() const; + + /** + * Applies force from the player. Only applies if there is currently an object attached to the rope. + * @param vecForce Force. + */ + void ApplyForceFromPlayer( const Vector& vecForce ); + + /** + * Applies force to a specific segment. + * @param vecForce Force. + * @param uiSegment Segment index. + */ + void ApplyForceToSegment( const Vector& vecForce, const size_t uiSegment ); + + /** + * Attached an object to the given segment. + */ + void AttachObjectToSegment( CRopeSegment* pSegment ); + + /** + * Detaches an attached object. + */ + void DetachObject(); + + /** + * @return Whether an object is attached. + */ + bool IsObjectAttached() const { return mObjectAttached; } + + /** + * @return Whether this rope allows attachments. + */ + bool IsAcceptingAttachment() const; + + /** + * @return The number of segments. + */ + size_t GetNumSegments() const { return m_iSegments; } + + /** + * @return The segments. + */ + CRopeSegment** GetSegments() { return seg; } + + /** + * @return The alternative segments. + */ + CRopeSegment** GetAltSegments() { return altseg; } + + /** + * @return The toggle value. + */ + bool GetToggleValue() const { return m_bToggle; } + + /** + * @return Whether this rope is allowed to make sounds. + */ + bool IsSoundAllowed() const { return m_bMakeSound; } + + /** + * Sets whether this rope is allowed to make sounds. + */ + void SetSoundAllowed( const bool bAllowed ) + { + m_bMakeSound = bAllowed; + } + + /** + * @return Whether this rope should creak. + */ + bool ShouldCreak() const; + + /** + * Plays a creak sound. + */ + void Creak(); + + /** + * @return The body model name. + */ + string_t GetBodyModel() const { return mBodyModel; } + + /** + * @return The ending model name. + */ + string_t GetEndingModel() const { return mEndingModel; } + + /** + * @return Segment length for the given segment. + */ + float GetSegmentLength( size_t uiSegmentIndex ) const; + + /** + * @return Total rope length. + */ + float GetRopeLength() const; + + /** + * @return The rope's origin. + */ + Vector GetRopeOrigin() const; + + /** + * @return Whether the given segment index is valid. + */ + bool IsValidSegmentIndex( const size_t uiSegment ) const; + + /** + * @return The origin of the given segment. + */ + Vector GetSegmentOrigin( const size_t uiSegment ) const; + + /** + * @return The attachment point of the given segment. + */ + Vector GetSegmentAttachmentPoint( const size_t uiSegment ) const; + + /** + * @param pSegment Segment. + * Sets the attached object segment. + */ + void SetAttachedObjectsSegment( CRopeSegment* pSegment ); + + /** + * @param uiSegmentIndex Segment index. + * @return The segment direction normal from its origin. + */ + Vector GetSegmentDirFromOrigin( const size_t uiSegmentIndex ) const; + + /** + * @return The attached object position. + */ + Vector GetAttachedObjectsPosition() const; + +private: + size_t m_iSegments; + + CRopeSegment* seg[ MAX_SEGMENTS ]; + CRopeSegment* altseg[ MAX_SEGMENTS ]; + + bool m_bToggle; + + bool m_InitialDeltaTime; + + float mLastTime; + + Vector m_LastEndPos; + Vector m_Gravity; + float m_HookConstant; + float m_SpringDampning; + + CRopeSample* m_CurrentSys[ MAX_SAMPLES ]; + CRopeSample* m_TargetSys[ MAX_SAMPLES ]; + RopeSampleData* m_TempSys[ MAX_TEMP_SAMPLES ]; + + size_t m_NumSamples; + + Spring* m_Spring; + + size_t m_SpringCnt; + + bool mSpringsInitialized; + + int m_BeamOffset; + + bool mObjectAttached; + + size_t mAttachedObjectsSegment; + float mAttachedObjectsOffset; + float detachTime; + + string_t mBodyModel; + string_t mEndingModel; + + int mDisallowPlayerAttachment; + + bool m_bMakeSound; +}; + +#endif //ROPES_H diff --git a/dlls/player.cpp b/dlls/player.cpp index 30caccd9..0f6f7bb5 100644 --- a/dlls/player.cpp +++ b/dlls/player.cpp @@ -120,6 +120,7 @@ TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ), DEFINE_FIELD(CBasePlayer, m_fInXen, FIELD_BOOLEAN), + DEFINE_FIELD(CBasePlayer, m_pRope, FIELD_CLASSPTR), //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games @@ -1880,6 +1881,135 @@ void CBasePlayer::PreThink( void ) else pev->flags &= ~FL_ONTRAIN; + //We're on a rope. - Solokiller + if( (m_afPhysicsFlags & PFLAG_ONROPE) && m_pRope ) + { + pev->velocity = g_vecZero; + + Vector vecAttachPos = m_pRope->GetAttachedObjectsPosition(); + + pev->origin = vecAttachPos; + + Vector vecForce; + + /* + //This causes sideways acceleration that doesn't occur in Op4. - Solokiller + if( pev->button & IN_DUCK ) + { + vecForce.x = gpGlobals->v_right.x; + vecForce.y = gpGlobals->v_right.y; + vecForce.z = 0; + m_pRope->ApplyForceFromPlayer( vecForce ); + } + if( pev->button & IN_JUMP ) + { + vecForce.x = -gpGlobals->v_right.x; + vecForce.y = -gpGlobals->v_right.y; + vecForce.z = 0; + m_pRope->ApplyForceFromPlayer( vecForce ); + } + */ + + //Determine if any force should be applied to the rope, or if we should move around. - Solokiller + if( pev->button & ( IN_BACK | IN_FORWARD ) ) + { + if( ( gpGlobals->v_forward.x * gpGlobals->v_forward.x + + gpGlobals->v_forward.y * gpGlobals->v_forward.y - + gpGlobals->v_forward.z * gpGlobals->v_forward.z ) <= 0.0 ) + { + if( m_bIsClimbing ) + { + const float flDelta = gpGlobals->time - m_flLastClimbTime; + m_flLastClimbTime = gpGlobals->time; + if( pev->button & IN_FORWARD ) + { + if( gpGlobals->v_forward.z < 0.0 ) + { + if( !m_pRope->MoveDown( flDelta ) ) + { + //Let go of the rope, detach. - Solokiller + pev->movetype = MOVETYPE_WALK; + pev->solid = SOLID_SLIDEBOX; + + m_afPhysicsFlags &= ~PFLAG_ONROPE; + m_pRope->DetachObject(); + m_pRope = NULL; + m_bIsClimbing = false; + } + } + else + { + m_pRope->MoveUp( flDelta ); + } + } + if( pev->button & IN_BACK ) + { + if( gpGlobals->v_forward.z < 0.0 ) + { + m_pRope->MoveUp( flDelta ); + } + else if( !m_pRope->MoveDown( flDelta ) ) + { + //Let go of the rope, detach. - Solokiller + pev->movetype = MOVETYPE_WALK; + pev->solid = SOLID_SLIDEBOX; + m_afPhysicsFlags &= ~PFLAG_ONROPE; + m_pRope->DetachObject(); + m_pRope = NULL; + m_bIsClimbing = false; + } + } + } + else + { + m_bIsClimbing = true; + m_flLastClimbTime = gpGlobals->time; + } + } + else + { + vecForce.x = gpGlobals->v_forward.x; + vecForce.y = gpGlobals->v_forward.y; + vecForce.z = 0.0; + if( pev->button & IN_BACK ) + { + vecForce.x = -gpGlobals->v_forward.x; + vecForce.y = -gpGlobals->v_forward.y; + vecForce.z = 0; + } + m_pRope->ApplyForceFromPlayer( vecForce ); + m_bIsClimbing = false; + } + } + else + { + m_bIsClimbing = false; + } + + if( m_afButtonPressed & IN_JUMP ) + { + //We've jumped off the rope, give us some momentum - Solokiller + pev->movetype = MOVETYPE_WALK; + pev->solid = SOLID_SLIDEBOX; + this->m_afPhysicsFlags &= ~PFLAG_ONROPE; + + Vector vecDir = gpGlobals->v_up * 165.0 + gpGlobals->v_forward * 150.0; + + Vector vecVelocity = m_pRope->GetAttachedObjectsVelocity() * 2; + + vecVelocity = vecVelocity.Normalize(); + + vecVelocity = vecVelocity * 200; + + pev->velocity = vecVelocity + vecDir; + + m_pRope->DetachObject(); + m_pRope = NULL; + m_bIsClimbing = false; + } + return; + } + // Train speed control if( m_afPhysicsFlags & PFLAG_ONTRAIN ) { diff --git a/dlls/player.h b/dlls/player.h index 48b75aff..a933d8a5 100644 --- a/dlls/player.h +++ b/dlls/player.h @@ -17,6 +17,7 @@ #define PLAYER_H #include "pm_materials.h" +#include "ropes.h" #define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet #define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet @@ -37,6 +38,8 @@ #define PFLAG_LATCHING ( 1<<6 ) // Player is latching to a target #define PFLAG_ATTACHED ( 1<<7 ) // Player is attached by a barnacle tongue tip + +#define PFLAG_ONROPE ( 1<<8 ) // // generic player // @@ -374,6 +377,28 @@ public: void Service_Grapple( void ); bool m_bSentBhopcap; // If false, the player just joined and needs a bhopcap message. + + bool m_bIsClimbing; + float m_flLastClimbTime; + CRope *m_pRope; + BOOL IsOnRope() + { + return ( m_afPhysicsFlags & PFLAG_ONROPE ) != 0; + } + + void SetRope( CBaseEntity *pRope ) + { + m_pRope = (CRope*)pRope; + } + void SetOnRopeState( bool onRope ) + { + if( onRope ) + m_afPhysicsFlags |= PFLAG_ONROPE; + else + m_afPhysicsFlags &= ~PFLAG_ONROPE; + + } + CRope* GetRope() { return m_pRope; } }; #define AUTOAIM_2DEGREES 0.0348994967025