//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "tf_walker_strider.h" #include "npcevent.h" #include "engine/IEngineSound.h" #include "tf_gamerules.h" #include "shake.h" #include "in_buttons.h" #include "studio.h" #define STRIDER_AE_FOOTSTEP_LEFT 1 #define STRIDER_AE_FOOTSTEP_RIGHT 2 #define STRIDER_AE_FOOTSTEP_BACK 3 #define STRIDER_AE_FOOTSTEP_LEFTM 4 #define STRIDER_AE_FOOTSTEP_RIGHTM 5 #define STRIDER_AE_FOOTSTEP_BACKM 6 #define STRIDER_AE_FOOTSTEP_LEFTL 7 #define STRIDER_AE_FOOTSTEP_RIGHTL 8 #define STRIDER_AE_FOOTSTEP_BACKL 9 // How many inches/sec the strider's torso moves up and down to get the torso at the right height. #define STRIDER_TORSO_VERTICAL_SLIDE_SPEED 100 #define STRIDER_FIRE_TIME 5 #define STRIDER_FIRE_INTERVAL 0.3 #define STRIDER_FIRE_ANGLE_ERROR 6 // N degrees of error when he fires. #define WALKER_STRIDER_MODEL "models/objects/walker_strider.mdl" IMPLEMENT_SERVERCLASS_ST( CWalkerStrider, DT_WalkerStrider ) SendPropInt( SENDINFO( m_bCrouched ), 1, SPROP_UNSIGNED ) END_SEND_TABLE() LINK_ENTITY_TO_CLASS( walker_strider, CWalkerStrider ); PRECACHE_REGISTER( walker_strider ); char *g_StriderFeet[] = { "left foot", "right foot", "back foot" }; #define NUM_STRIDER_FEET ARRAYSIZE( g_StriderFeet ) CWalkerStrider::CWalkerStrider() { m_bCrouched = false; m_flOriginToLowestLegHeight = -1; m_flWantedZ = -1; m_bFiring = false; m_vDriverAngles.Init(); } void CWalkerStrider::Precache() { PrecacheModel( WALKER_STRIDER_MODEL ); PrecacheScriptSound( "Brush.Footstep" ); PrecacheScriptSound( "Brush.Fire" ); BaseClass::Precache(); } void CWalkerStrider::Spawn() { SpawnWalker( WALKER_STRIDER_MODEL, // Model name. OBJ_WALKER_STRIDER, // Object type. Vector( -186, -157, -504 ), // Placement dimensions. Vector( 194, 159, 41 ), 200, // Health. 1, // Max passengers. 2.0 // 2x animation speed boost ); } bool CWalkerStrider::IsPassengerVisible( int nRole ) { if ( nRole == VEHICLE_ROLE_DRIVER ) return false; return true; } void CWalkerStrider::Fire() { EnableWalkMode( false ); m_bFiring = true; m_flFireEndTime = gpGlobals->curtime + STRIDER_FIRE_TIME; ResetSequence( LookupSequence( "shoot01a" ) ); m_flAnimTime = m_flPrevAnimTime = 0; SetCycle( 0 ); m_flNextShootTime = gpGlobals->curtime; m_vFireAngles[ROLL] = 0; m_vFireAngles[PITCH] = m_vDriverAngles[PITCH]; m_vFireAngles[YAW] = GetAbsAngles()[YAW]; // Play the fire sound. CPASAttenuationFilter filter( this, "Brush.Fire" ); EmitSound( filter, 0, "Brush.Fire", &GetAbsOrigin() ); } void CWalkerStrider::Crouch() { // Disable the base class's walking functionality while we're crouched. EnableWalkMode( false ); m_bCrouched = true; m_flNextCrouchTime = gpGlobals->curtime + 0.3; ResetSequence( LookupSequence( "low" ) ); // HACK: there should be a better way to this.. like CBaseAnimating::ResetAnimation, // or ResetSequence should do it. m_flAnimTime = m_flPrevAnimTime = 0; SetCycle( 0 ); // HACK CITY.. This forces it to invalidate the abs origins of the gun bases. Vector vPos = GetLocalOrigin(); SetLocalOrigin( vPos + Vector( 100, 0, 0 ) ); SetLocalOrigin( vPos ); } void CWalkerStrider::UnCrouch() { EnableWalkMode( true ); m_flNextCrouchTime = gpGlobals->curtime + 0.3; m_bCrouched = false; // HACK CITY.. This forces it to invalidate the abs origins of the gun bases. Vector vPos = GetLocalOrigin(); SetLocalOrigin( vPos + Vector( 100, 0, 0 ) ); SetLocalOrigin( vPos ); } void CWalkerStrider::WalkerThink() { float dt = GetTimeDelta(); BaseClass::WalkerThink(); if ( m_bCrouched ) { // Just sit there until they hit IN_DUCK again. if ( (m_LastButtons & IN_DUCK) && gpGlobals->curtime > m_flNextCrouchTime ) { UnCrouch(); } } else { if ( gpGlobals->curtime > m_flNextCrouchTime ) { if ( m_LastButtons & IN_DUCK ) { // They want to crouch. Crouch(); } else if ( !m_bFiring && (m_LastButtons & IN_ATTACK) ) { Fire(); } } } m_vDriverAngles = m_vLastCmdViewAngles; if ( m_bCrouched ) { // The "low" animation gets back up at the end so don't let that happen. SetCycle( MIN( GetCycle(), 0.5f ) ); } else if ( m_bFiring ) { if ( gpGlobals->curtime > m_flFireEndTime ) { m_bFiring = false; EnableWalkMode( true ); } else { // Shoot? if ( gpGlobals->curtime >= m_flNextShootTime ) { Vector vSrc = GetAbsOrigin(); Vector vForward; QAngle vFireAngles = m_vFireAngles; vFireAngles[YAW] += RandomFloat( -STRIDER_FIRE_ANGLE_ERROR, STRIDER_FIRE_ANGLE_ERROR ); vFireAngles[PITCH] += RandomFloat( -STRIDER_FIRE_ANGLE_ERROR, STRIDER_FIRE_ANGLE_ERROR ); AngleVectors( vFireAngles, &vForward ); trace_t trace; UTIL_TraceLine( vSrc, vSrc + vForward * 2000, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace ); Vector vHitPos = trace.endpos; float flDamageRadius = 100; float flDamage = 50; CBasePlayer *pDriver = GetPassenger( VEHICLE_ROLE_DRIVER ); if ( pDriver ) { UTIL_ImpactTrace( &trace, DMG_ENERGYBEAM, "Strider" ); // Tell the client entity to make a beam effect. EntityMessageBegin( this, true ); WRITE_VEC3COORD( vHitPos ); MessageEnd(); UTIL_ScreenShake( vHitPos, 10.0, 150.0, 1.0, 100, SHAKE_START ); RadiusDamage( CTakeDamageInfo( this, pDriver, flDamage, DMG_BLAST ), vHitPos, flDamageRadius, CLASS_NONE, NULL ); } m_flNextShootTime = gpGlobals->curtime + STRIDER_FIRE_INTERVAL; } } } else { // Move our torso within range of our feet. if ( m_flOriginToLowestLegHeight != -1 ) { trace_t trace; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 2000 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trace ); if ( trace.fraction < 1 ) { m_flWantedZ = trace.endpos.z + m_flOriginToLowestLegHeight; } // Move our Z towards the wanted Z. if ( m_flWantedZ != -1 ) { Vector vCur = GetAbsOrigin(); vCur.z = Approach( m_flWantedZ, vCur.z, STRIDER_TORSO_VERTICAL_SLIDE_SPEED * dt ); SetAbsOrigin( vCur ); } } } } void CWalkerStrider::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { // Sapper removal if ( RemoveEnemyAttachments( pActivator ) ) return; CBaseTFPlayer *pPlayer = dynamic_cast(pActivator); if ( !pPlayer || !InSameTeam( pPlayer ) ) return; // Ok, put them in the driver role. AttemptToBoardVehicle( pPlayer ); } void CWalkerStrider::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move ) { BaseClass::SetupMove( pPlayer, ucmd, pHelper, move ); } // // // This is a TOTAL hack, but we don't have any nodes that work well at all for mounted guns. // This all goes away when we get a new model. // // float sideDist = 90; float downDist = -400; bool CWalkerStrider::GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld ) { CStudioHdr *pStudioHdr = GetModelPtr( ); if ( !pStudioHdr || iAttachment < 1 || iAttachment > pStudioHdr->GetNumAttachments() ) { return false; } Vector vLocalPos( 0, 0, 0 ); const mstudioattachment_t &pAttachment = pStudioHdr->pAttachment( iAttachment-1 ); if ( stricmp( pAttachment.pszName(), "build_point_left_gun" ) == 0 ) { vLocalPos.y = sideDist; } else if ( stricmp( pAttachment.pszName(), "build_point_right_gun" ) == 0 ) { vLocalPos.y = -sideDist; } else if ( stricmp( pAttachment.pszName(), "ThirdPersonCameraOrigin" ) == 0 ) { } else { // Ok, it's not one of our magical attachments. Use the regular attachment setup stuff. return BaseClass::GetAttachment( iAttachment, attachmentToWorld ); } if ( m_bCrouched ) { vLocalPos.z += downDist; } // Now build the output matrix. matrix3x4_t localMatrix; SetIdentityMatrix( localMatrix ); PositionMatrix( vLocalPos, localMatrix ); ConcatTransforms( EntityToWorldTransform(), localMatrix, attachmentToWorld ); return true; } bool CWalkerStrider::StartBuilding( CBaseEntity *pBuilder ) { if ( !BaseClass::StartBuilding( pBuilder ) ) return false; // Now figure out our ideal Z distance from our lowest foot to our torso. Vector vOrigin; QAngle vAngles; BaseClass::GetAttachment( "left foot", vOrigin, vAngles ); m_flOriginToLowestLegHeight = GetAbsOrigin().z - vOrigin.z; m_flOriginToLowestLegHeight -= 40; // fudge it a little so the legs have room to stretch. return true; } void CWalkerStrider::FootHit( const char *pFootName ) { if ( m_bCrouched || gpGlobals->curtime > m_flDontMakeSoundsUntil ) { Vector footPosition; QAngle angles; ((BaseClass*)this)->GetAttachment( pFootName, footPosition, angles ); CPASAttenuationFilter filter( this, "Brush.Footstep" ); EmitSound( filter, entindex(), "Brush.Footstep", &footPosition ); UTIL_ScreenShake( footPosition, 20.0, 1.0, 0.5, 200, SHAKE_START, false ); CBasePlayer *pPlayer = GetPassenger( VEHICLE_ROLE_DRIVER ); if ( pPlayer ) { CTakeDamageInfo info( this, pPlayer, 20, DMG_CLUB ); TFGameRules()->RadiusDamage( info, footPosition, 200, CLASS_NONE ); } m_flDontMakeSoundsUntil = gpGlobals->curtime + 0.4; } } void CWalkerStrider::HandleAnimEvent( animevent_t *pEvent ) { switch( pEvent->event ) { case STRIDER_AE_FOOTSTEP_LEFT: case STRIDER_AE_FOOTSTEP_LEFTM: case STRIDER_AE_FOOTSTEP_LEFTL: FootHit( "left foot" ); break; case STRIDER_AE_FOOTSTEP_RIGHT: case STRIDER_AE_FOOTSTEP_RIGHTM: case STRIDER_AE_FOOTSTEP_RIGHTL: FootHit( "right foot" ); break; case STRIDER_AE_FOOTSTEP_BACK: case STRIDER_AE_FOOTSTEP_BACKM: case STRIDER_AE_FOOTSTEP_BACKL: FootHit( "back foot" ); break; } }