mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-08 01:39:36 +00:00
1
This commit is contained in:
@@ -0,0 +1,621 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: FIXME: This will ultimately become a more generic implementation
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "cbase.h"
|
||||
#include "ai_memory.h"
|
||||
#include "ai_speech.h"
|
||||
#include "ai_behavior.h"
|
||||
#include "ai_navigator.h"
|
||||
#include "ai_playerally.h"
|
||||
#include "ai_behavior_follow.h"
|
||||
#include "ai_moveprobe.h"
|
||||
|
||||
#include "ai_behavior_alyx_injured.h"
|
||||
|
||||
ConVar g_debug_injured_follow( "g_debug_injured_follow", "0" );
|
||||
ConVar injured_help_plee_range( "injured_help_plee_range", "256" );
|
||||
|
||||
#define TLK_INJURED_FOLLOW_TOO_FAR "TLK_INJURED_FOLLOW_TOO_FAR"
|
||||
|
||||
BEGIN_DATADESC( CAI_BehaviorAlyxInjured )
|
||||
DEFINE_FIELD( m_flNextWarnTime, FIELD_TIME ),
|
||||
// m_ActivityMap
|
||||
|
||||
END_DATADESC();
|
||||
|
||||
Activity ACT_INJURED_COWER;
|
||||
Activity ACT_GESTURE_INJURED_COWER_FLINCH;
|
||||
|
||||
#define COVER_DISTANCE 128.0f // Distance behind target to find cover
|
||||
#define MIN_ENEMY_MOB 3 // Number of enemies considerd overwhelming
|
||||
#define MAX_DIST_FROM_FOLLOW_TARGET 256 // If the follow target is farther than this, the NPC will run to it
|
||||
|
||||
//=============================================================================
|
||||
|
||||
CAI_BehaviorAlyxInjured::CAI_BehaviorAlyxInjured( void ) : m_flNextWarnTime( 0.0f )
|
||||
{
|
||||
SetDefLessFunc( m_ActivityMap );
|
||||
}
|
||||
|
||||
struct ActivityMap_t
|
||||
{
|
||||
Activity activity;
|
||||
Activity translation;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_BehaviorAlyxInjured::PopulateActivityMap( void )
|
||||
{
|
||||
// Maps one activity to a translated one
|
||||
ActivityMap_t map[] =
|
||||
{
|
||||
// Runs
|
||||
{ ACT_RUN, ACT_RUN_HURT },
|
||||
{ ACT_RUN_AIM, ACT_RUN_AIM }, // FIMXE: No appropriate temp anim right now!
|
||||
{ ACT_RUN_CROUCH, ACT_RUN_HURT },
|
||||
{ ACT_RUN_CROUCH_AIM, ACT_RUN_HURT },
|
||||
{ ACT_RUN_PROTECTED, ACT_RUN_HURT },
|
||||
{ ACT_RUN_RELAXED, ACT_RUN_HURT },
|
||||
{ ACT_RUN_STIMULATED, ACT_RUN_HURT },
|
||||
{ ACT_RUN_AGITATED, ACT_RUN_HURT },
|
||||
{ ACT_RUN_AIM_RELAXED, ACT_RUN_AIM_RELAXED }, // FIMXE: No appropriate temp anim right now!
|
||||
{ ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_STIMULATED }, // FIMXE: No appropriate temp anim right now!
|
||||
{ ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_AGITATED }, // FIMXE: No appropriate temp anim right now!
|
||||
{ ACT_RUN_HURT, ACT_RUN_HURT },
|
||||
|
||||
// Walks
|
||||
{ ACT_WALK, ACT_WALK_HURT },
|
||||
{ ACT_WALK_AIM, ACT_WALK_HURT },
|
||||
{ ACT_WALK_CROUCH, ACT_WALK_HURT },
|
||||
{ ACT_WALK_CROUCH_AIM, ACT_WALK_HURT },
|
||||
{ ACT_WALK_RELAXED, ACT_WALK_HURT },
|
||||
{ ACT_WALK_STIMULATED, ACT_WALK_HURT },
|
||||
{ ACT_WALK_AGITATED, ACT_WALK_HURT },
|
||||
{ ACT_WALK_AIM_RELAXED, ACT_WALK_HURT },
|
||||
{ ACT_WALK_AIM_STIMULATED, ACT_WALK_HURT },
|
||||
{ ACT_WALK_AIM_AGITATED, ACT_WALK_HURT },
|
||||
{ ACT_WALK_HURT, ACT_WALK_HURT },
|
||||
|
||||
{ ACT_IDLE, ACT_IDLE_HURT },
|
||||
{ ACT_COVER_LOW, ACT_INJURED_COWER },
|
||||
{ ACT_COWER, ACT_INJURED_COWER },
|
||||
};
|
||||
|
||||
// Clear the map
|
||||
m_ActivityMap.RemoveAll();
|
||||
|
||||
// Add all translations
|
||||
for ( int i = 0; i < ARRAYSIZE( map ); i++ )
|
||||
{
|
||||
Assert( m_ActivityMap.Find( map[i].activity ) == m_ActivityMap.InvalidIndex() );
|
||||
m_ActivityMap.Insert( map[i].activity, map[i].translation );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Populate the list after save/load
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_BehaviorAlyxInjured::OnRestore( void )
|
||||
{
|
||||
PopulateActivityMap();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Populate the list on spawn
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_BehaviorAlyxInjured::Spawn( void )
|
||||
{
|
||||
PopulateActivityMap();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the flinch activity for us to play
|
||||
// Input : bHeavyDamage -
|
||||
// bGesture -
|
||||
// Output : Activity
|
||||
//-----------------------------------------------------------------------------
|
||||
Activity CAI_BehaviorAlyxInjured::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
|
||||
{
|
||||
//
|
||||
if ( ( bGesture == false ) || ( GetOuter()->GetActivity() != ACT_COWER ) )
|
||||
return BaseClass::GetFlinchActivity( bHeavyDamage, bGesture );
|
||||
|
||||
// Translate the flinch if we're cowering
|
||||
return ACT_GESTURE_INJURED_COWER_FLINCH;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : nActivity -
|
||||
//-----------------------------------------------------------------------------
|
||||
Activity CAI_BehaviorAlyxInjured::NPC_TranslateActivity( Activity nActivity )
|
||||
{
|
||||
// Find out what the base class wants to do with the activity
|
||||
Activity nNewActivity = BaseClass::NPC_TranslateActivity( nActivity );
|
||||
|
||||
// Look it up in the translation map
|
||||
int nIndex = m_ActivityMap.Find( nNewActivity );
|
||||
|
||||
if ( m_ActivityMap.IsValidIndex( nIndex ) )
|
||||
return m_ActivityMap[nIndex];
|
||||
|
||||
return nNewActivity;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Determines if Alyx should run away from enemies or stay put
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_BehaviorAlyxInjured::ShouldRunToCover( void )
|
||||
{
|
||||
Vector vecRetreatPos;
|
||||
float flRetreatRadius = 128.0f;
|
||||
|
||||
// See how far off from our cover position we are
|
||||
if ( FindCoverFromEnemyBehindTarget( GetFollowTarget(), flRetreatRadius, &vecRetreatPos ) )
|
||||
{
|
||||
float flDestDistSqr = ( GetOuter()->WorldSpaceCenter() - vecRetreatPos ).LengthSqr();
|
||||
if ( flDestDistSqr > Square( flRetreatRadius ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: See if we need to follow our goal
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_BehaviorAlyxInjured::ShouldRunToFollowGoal( void )
|
||||
{
|
||||
// If we're too far from our follow target, we need to chase after them
|
||||
float flDistToFollowGoalSqr = ( GetOuter()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin() ).LengthSqr();
|
||||
if ( flDistToFollowGoalSqr > Square(MAX_DIST_FROM_FOLLOW_TARGET) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Translate base schedules into overridden forms
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_BehaviorAlyxInjured::TranslateSchedule( int scheduleType )
|
||||
{
|
||||
switch( scheduleType )
|
||||
{
|
||||
case SCHED_RUN_FROM_ENEMY:
|
||||
case SCHED_RUN_FROM_ENEMY_MOB:
|
||||
{
|
||||
// Get under cover if we're able to
|
||||
if ( ShouldRunToCover() )
|
||||
return SCHED_INJURED_RUN_FROM_ENEMY;
|
||||
|
||||
// Run to our follow goal if we're too far away from it
|
||||
if ( ShouldRunToFollowGoal() )
|
||||
return SCHED_FOLLOW;
|
||||
|
||||
// Cower if surrounded
|
||||
if ( HasCondition( COND_INJURED_OVERWHELMED ) )
|
||||
return SCHED_INJURED_COWER;
|
||||
|
||||
// Face our enemies
|
||||
return SCHED_INJURED_FEAR_FACE;
|
||||
}
|
||||
break;
|
||||
|
||||
case SCHED_RUN_FROM_ENEMY_FALLBACK:
|
||||
return SCHED_INJURED_COWER;
|
||||
break;
|
||||
}
|
||||
|
||||
return BaseClass::TranslateSchedule( scheduleType );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Pick up failure cases and handle them
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_BehaviorAlyxInjured::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
|
||||
{
|
||||
// Failed schedules
|
||||
switch( failedSchedule )
|
||||
{
|
||||
case SCHED_RUN_FROM_ENEMY:
|
||||
case SCHED_RUN_FROM_ENEMY_MOB:
|
||||
case SCHED_FOLLOW:
|
||||
return SCHED_INJURED_COWER;
|
||||
}
|
||||
|
||||
// Failed tasks
|
||||
switch( failedTask )
|
||||
{
|
||||
case TASK_FIND_COVER_FROM_ENEMY:
|
||||
case TASK_FIND_INJURED_COVER_FROM_ENEMY:
|
||||
|
||||
// Only cower if we're already near enough to our follow target
|
||||
float flDistToFollowTargetSqr = ( GetOuter()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin() ).LengthSqr();
|
||||
if ( flDistToFollowTargetSqr > Square( 256 ) )
|
||||
return SCHED_FOLLOW;
|
||||
|
||||
return SCHED_INJURED_COWER;
|
||||
break;
|
||||
}
|
||||
|
||||
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Find the general direction enemies are coming towards us at
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_BehaviorAlyxInjured::FindThreatDirection2D( const Vector &vecSource, Vector *vecOut )
|
||||
{
|
||||
// Find the general direction our threat is coming from
|
||||
bool bValid = false;
|
||||
Vector vecScratch;
|
||||
AIEnemiesIter_t iter;
|
||||
|
||||
// Iterate through all known enemies
|
||||
for( AI_EnemyInfo_t *pMemory = GetOuter()->GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetOuter()->GetEnemies()->GetNext(&iter) )
|
||||
{
|
||||
if ( pMemory == NULL || pMemory->hEnemy == NULL )
|
||||
continue;
|
||||
|
||||
vecScratch = ( vecSource - pMemory->hEnemy->WorldSpaceCenter() );
|
||||
VectorNormalize( vecScratch );
|
||||
|
||||
(*vecOut) += vecScratch;
|
||||
bValid = true;
|
||||
}
|
||||
|
||||
// Find the general direction
|
||||
(*vecOut).z = 0.0f;
|
||||
VectorNormalize( (*vecOut) );
|
||||
return bValid;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Find a position that hides us from our threats while interposing the
|
||||
// target entity between us and the threat
|
||||
// Input : pTarget - entity to hide behind
|
||||
// flRadius - Radius around the target to search
|
||||
// *vecOut - position
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_BehaviorAlyxInjured::FindCoverFromEnemyBehindTarget( CBaseEntity *pTarget, float flRadius, Vector *vecOut )
|
||||
{
|
||||
if ( pTarget == NULL )
|
||||
return false;
|
||||
|
||||
Vector vecTargetPos = pTarget->GetAbsOrigin();
|
||||
Vector vecThreatDir = vec3_origin;
|
||||
|
||||
// Find our threat direction and base our cover on that
|
||||
if ( FindThreatDirection2D( vecTargetPos, &vecThreatDir ) )
|
||||
{
|
||||
// Get a general location for taking cover
|
||||
Vector vecTestPos = vecTargetPos + ( vecThreatDir * flRadius );
|
||||
|
||||
if ( g_debug_injured_follow.GetBool() )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecTestPos, 8.0f, 255, 255, 0, 32, true, 2.0f );
|
||||
}
|
||||
|
||||
// Make sure we never move towards our threat to get to cover!
|
||||
Vector vecMoveDir = GetOuter()->GetAbsOrigin() - vecTestPos;
|
||||
VectorNormalize( vecMoveDir );
|
||||
float flDotToCover = DotProduct( vecMoveDir, vecThreatDir );
|
||||
if ( flDotToCover > 0.0f )
|
||||
{
|
||||
if ( g_debug_injured_follow.GetBool() )
|
||||
{
|
||||
NDebugOverlay::HorzArrow( GetOuter()->GetAbsOrigin(), vecTestPos, 8.0f, 255, 0, 0, 32, true, 2.0f );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AIMoveTrace_t moveTrace;
|
||||
GetOuter()->GetMoveProbe()->MoveLimit( NAV_GROUND,
|
||||
GetOuter()->GetAbsOrigin(),
|
||||
vecTestPos,
|
||||
MASK_SOLID_BRUSHONLY,
|
||||
NULL,
|
||||
0,
|
||||
&moveTrace );
|
||||
|
||||
bool bWithinRangeToGoal = ( moveTrace.vEndPosition - vecTestPos ).Length2DSqr() < Square( GetOuter()->GetHullWidth() * 3.0f );
|
||||
bool bCanStandAtGoal = GetOuter()->GetMoveProbe()->CheckStandPosition( moveTrace.vEndPosition, MASK_SOLID_BRUSHONLY );
|
||||
|
||||
if ( bWithinRangeToGoal == false || bCanStandAtGoal == false )
|
||||
{
|
||||
if ( g_debug_injured_follow.GetBool() )
|
||||
{
|
||||
NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), vecTestPos, GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 255, 0, 0, 0, 2.0f );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Accept it
|
||||
*vecOut = moveTrace.vEndPosition;
|
||||
|
||||
if ( g_debug_injured_follow.GetBool() )
|
||||
{
|
||||
NDebugOverlay::SweptBox( GetOuter()->GetAbsOrigin(), (*vecOut), GetOuter()->GetHullMins(), GetOuter()->GetHullMaxs(), vec3_angle, 0, 255, 0, 0, 2.0f );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *pTask -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_BehaviorAlyxInjured::StartTask( const Task_t *pTask )
|
||||
{
|
||||
switch( pTask->iTask )
|
||||
{
|
||||
case TASK_FIND_COVER_FROM_ENEMY:
|
||||
{
|
||||
CBaseEntity *pLeader = GetFollowTarget();
|
||||
if ( !pLeader )
|
||||
{
|
||||
BaseClass::StartTask( pTask );
|
||||
break;
|
||||
}
|
||||
|
||||
// Find a position behind our follow target
|
||||
Vector coverPos = vec3_invalid;
|
||||
if ( FindCoverFromEnemyBehindTarget( pLeader, COVER_DISTANCE, &coverPos ) )
|
||||
{
|
||||
AI_NavGoal_t goal( GOALTYPE_LOCATION, coverPos, ACT_RUN, AIN_HULL_TOLERANCE, AIN_DEF_FLAGS );
|
||||
GetOuter()->GetNavigator()->SetGoal( goal );
|
||||
GetOuter()->m_flMoveWaitFinished = gpGlobals->curtime + pTask->flTaskData;
|
||||
TaskComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
// Couldn't find anything
|
||||
TaskFail( FAIL_NO_COVER );
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
BaseClass::StartTask( pTask );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Whether or not Alyx is injured
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_BehaviorAlyxInjured::IsInjured( void ) const
|
||||
{
|
||||
return IsAlyxInInjuredMode();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_BehaviorAlyxInjured::GatherConditions( void )
|
||||
{
|
||||
BaseClass::GatherConditions();
|
||||
|
||||
// Always stomp over this
|
||||
ClearCondition( COND_INJURED_TOO_FAR_FROM_PLAYER );
|
||||
ClearCondition( COND_INJURED_OVERWHELMED );
|
||||
|
||||
// See if we're overwhelmed by foes
|
||||
if ( NumKnownEnemiesInRadius( GetOuter()->GetAbsOrigin(), COVER_DISTANCE ) >= MIN_ENEMY_MOB )
|
||||
{
|
||||
SetCondition( COND_INJURED_OVERWHELMED );
|
||||
}
|
||||
|
||||
// Determines whether we consider ourselves in danger
|
||||
bool bInDanger = ( HasCondition( COND_LIGHT_DAMAGE ) ||
|
||||
HasCondition( COND_HEAVY_DAMAGE ) ||
|
||||
HasCondition( COND_INJURED_OVERWHELMED ) );
|
||||
|
||||
// See if we're too far away from the player and in danger
|
||||
if ( AI_IsSinglePlayer() && bInDanger )
|
||||
{
|
||||
bool bWarnPlayer = false;
|
||||
|
||||
// This only works in single-player
|
||||
CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
|
||||
if ( pPlayer != NULL )
|
||||
{
|
||||
// FIXME: This distance may need to be the length of the shortest walked path between the follower and the target
|
||||
|
||||
// Get our approximate distance to the player
|
||||
float flDistToPlayer = UTIL_DistApprox2D( GetOuter()->GetAbsOrigin(), pPlayer->GetAbsOrigin() );
|
||||
if ( flDistToPlayer > injured_help_plee_range.GetFloat() )
|
||||
{
|
||||
bWarnPlayer = true;
|
||||
}
|
||||
else if ( flDistToPlayer > (injured_help_plee_range.GetFloat()*0.5f) && HasCondition( COND_SEE_PLAYER ) == false )
|
||||
{
|
||||
// Cut our distance in half if we can't see the player
|
||||
bWarnPlayer = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Yell for help!
|
||||
if ( bWarnPlayer )
|
||||
{
|
||||
// FIXME: This should be routed through the normal speaking code with a system to emit from the player's suit.
|
||||
|
||||
CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
|
||||
//float flPlayerDistSqr = ( GetOuter()->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).LengthSqr();
|
||||
|
||||
// If the player is too far away or we can't see him
|
||||
//if ( HasCondition( COND_SEE_PLAYER ) == false || flPlayerDistSqr > Square( 128 ) )
|
||||
{
|
||||
if ( m_flNextWarnTime < gpGlobals->curtime )
|
||||
{
|
||||
pPlayer->EmitSound( "npc_alyx.injured_too_far" );
|
||||
m_flNextWarnTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 5.0f );
|
||||
}
|
||||
}
|
||||
/*
|
||||
else
|
||||
{
|
||||
SpeakIfAllowed( TLK_INJURED_FOLLOW_TOO_FAR );
|
||||
m_flNextWarnTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 5.0f );
|
||||
}
|
||||
*/
|
||||
|
||||
SetCondition( COND_INJURED_TOO_FAR_FROM_PLAYER );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Speak a concept if we're able to
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_BehaviorAlyxInjured::SpeakIfAllowed( AIConcept_t concept )
|
||||
{
|
||||
CAI_Expresser *pExpresser = GetOuter()->GetExpresser();
|
||||
if ( pExpresser == NULL )
|
||||
return;
|
||||
|
||||
// Must be able to speak the concept
|
||||
if ( pExpresser->CanSpeakConcept( concept ) )
|
||||
{
|
||||
pExpresser->Speak( concept );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the number of known enemies within a radius to a point
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_BehaviorAlyxInjured::NumKnownEnemiesInRadius( const Vector &vecSource, float flRadius )
|
||||
{
|
||||
int nNumEnemies = 0;
|
||||
float flRadiusSqr = Square( flRadius );
|
||||
|
||||
AIEnemiesIter_t iter;
|
||||
|
||||
// Iterate through all known enemies
|
||||
for( AI_EnemyInfo_t *pMemory = GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetEnemies()->GetNext(&iter) )
|
||||
{
|
||||
if ( pMemory == NULL || pMemory->hEnemy == NULL )
|
||||
continue;
|
||||
|
||||
// Must hate or fear them
|
||||
if ( GetOuter()->IRelationType( pMemory->hEnemy ) != D_HT && GetOuter()->IRelationType( pMemory->hEnemy ) != D_FR )
|
||||
continue;
|
||||
|
||||
// Count only the enemies I've seen recently
|
||||
if ( gpGlobals->curtime - pMemory->timeLastSeen > 0.5f )
|
||||
continue;
|
||||
|
||||
// Must be within the radius we've specified
|
||||
float flEnemyDistSqr = ( vecSource - pMemory->hEnemy->GetAbsOrigin() ).Length2DSqr();
|
||||
if ( flEnemyDistSqr < flRadiusSqr )
|
||||
{
|
||||
nNumEnemies++;
|
||||
}
|
||||
}
|
||||
|
||||
return nNumEnemies;
|
||||
}
|
||||
|
||||
// ----------------------------------------------
|
||||
// Custom AI declarations
|
||||
// ----------------------------------------------
|
||||
|
||||
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_BehaviorAlyxInjured )
|
||||
{
|
||||
DECLARE_ACTIVITY( ACT_GESTURE_INJURED_COWER_FLINCH )
|
||||
DECLARE_ACTIVITY( ACT_INJURED_COWER )
|
||||
|
||||
DECLARE_CONDITION( COND_INJURED_TOO_FAR_FROM_PLAYER )
|
||||
DECLARE_CONDITION( COND_INJURED_OVERWHELMED )
|
||||
|
||||
DECLARE_TASK( TASK_FIND_INJURED_COVER_FROM_ENEMY )
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_INJURED_COWER,
|
||||
|
||||
" Tasks"
|
||||
// TOOD: Announce cower
|
||||
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_COWER"
|
||||
" TASK_WAIT 2"
|
||||
""
|
||||
" Interrupts"
|
||||
" COND_GIVE_WAY"
|
||||
" COND_PLAYER_PUSHING"
|
||||
)
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_INJURED_FEAR_FACE,
|
||||
|
||||
" Tasks"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // FIXME: Scared idle?
|
||||
" TASK_FACE_ENEMY 0"
|
||||
""
|
||||
" Interrupts"
|
||||
" COND_GIVE_WAY"
|
||||
" COND_PLAYER_PUSHING"
|
||||
);
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_INJURED_RUN_FROM_ENEMY,
|
||||
|
||||
" Tasks"
|
||||
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_INJURED_COWER"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_FIND_COVER_FROM_ENEMY 0"
|
||||
" TASK_RUN_PATH 0"
|
||||
" TASK_WAIT_FOR_MOVEMENT 0"
|
||||
""
|
||||
" Interrupts"
|
||||
);
|
||||
|
||||
AI_END_CUSTOM_SCHEDULE_PROVIDER()
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CAI_InjuredFollowGoal
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
BEGIN_DATADESC( CAI_InjuredFollowGoal )
|
||||
END_DATADESC()
|
||||
|
||||
LINK_ENTITY_TO_CLASS( ai_goal_injured_follow, CAI_InjuredFollowGoal );
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
void CAI_InjuredFollowGoal::EnableGoal( CAI_BaseNPC *pAI )
|
||||
{
|
||||
CAI_BehaviorAlyxInjured *pBehavior;
|
||||
if ( !pAI->GetBehavior( &pBehavior ) )
|
||||
return;
|
||||
|
||||
if ( GetGoalEntity() == NULL )
|
||||
return;
|
||||
|
||||
pBehavior->SetFollowGoal( this );
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
||||
void CAI_InjuredFollowGoal::DisableGoal( CAI_BaseNPC *pAI )
|
||||
{
|
||||
CAI_BehaviorAlyxInjured *pBehavior;
|
||||
if ( !pAI->GetBehavior( &pBehavior ) )
|
||||
return;
|
||||
|
||||
pBehavior->ClearFollowGoal( this );
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: FIXME: This will ultimately become a more generic implementation
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AI_BEHAVIOR_ALYX_INJURED_H
|
||||
#define AI_BEHAVIOR_ALYX_INJURED_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "utlmap.h"
|
||||
|
||||
extern bool IsAlyxInInjuredMode( void );
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
class CAI_InjuredFollowGoal : public CAI_FollowGoal
|
||||
{
|
||||
DECLARE_CLASS( CAI_InjuredFollowGoal, CAI_FollowGoal );
|
||||
|
||||
public:
|
||||
|
||||
virtual void EnableGoal( CAI_BaseNPC *pAI );
|
||||
virtual void DisableGoal( CAI_BaseNPC *pAI );
|
||||
|
||||
DECLARE_DATADESC();
|
||||
};
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
class CAI_BehaviorAlyxInjured : public CAI_FollowBehavior
|
||||
{
|
||||
DECLARE_CLASS( CAI_BehaviorAlyxInjured, CAI_FollowBehavior );
|
||||
DECLARE_DATADESC();
|
||||
|
||||
public:
|
||||
CAI_BehaviorAlyxInjured( void );
|
||||
|
||||
virtual const char *GetName( void ) { return "AlyxInjuredFollow"; }
|
||||
virtual Activity NPC_TranslateActivity( Activity nActivity );
|
||||
virtual int TranslateSchedule( int scheduleType );
|
||||
virtual void Spawn( void );
|
||||
virtual void OnRestore( void );
|
||||
virtual void StartTask( const Task_t *pTask );
|
||||
virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
|
||||
virtual void GatherConditions( void );
|
||||
virtual Activity GetFlinchActivity( bool bHeavyDamage, bool bGesture );
|
||||
|
||||
enum
|
||||
{
|
||||
// Schedules
|
||||
SCHED_INJURED_COWER = BaseClass::NEXT_SCHEDULE,
|
||||
SCHED_INJURED_FEAR_FACE,
|
||||
SCHED_INJURED_RUN_FROM_ENEMY,
|
||||
NEXT_SCHEDULE,
|
||||
|
||||
// Tasks
|
||||
TASK_FIND_INJURED_COVER_FROM_ENEMY = BaseClass::NEXT_TASK,
|
||||
NEXT_TASK,
|
||||
|
||||
// Conditions
|
||||
COND_INJURED_TOO_FAR_FROM_PLAYER = BaseClass::NEXT_CONDITION,
|
||||
COND_INJURED_OVERWHELMED,
|
||||
NEXT_CONDITION
|
||||
};
|
||||
|
||||
bool IsReadinessCapable( void ) { return ( IsInjured() == false ); } // Never use the readiness system when injured
|
||||
bool IsInjured( void ) const;
|
||||
|
||||
private:
|
||||
|
||||
void SpeakIfAllowed( AIConcept_t concept );
|
||||
bool ShouldRunToCover( void );
|
||||
bool ShouldRunToFollowGoal( void );
|
||||
bool FindThreatDirection2D( const Vector &vecSource, Vector *vecOut );
|
||||
bool FindCoverFromEnemyBehindTarget( CBaseEntity *pTarget, float flRadius, Vector *vecOut );
|
||||
void PopulateActivityMap( void );
|
||||
int NumKnownEnemiesInRadius( const Vector &vecSource, float flRadius );
|
||||
|
||||
CUtlMap<Activity,Activity> m_ActivityMap;
|
||||
|
||||
float m_flNextWarnTime;
|
||||
|
||||
protected:
|
||||
DEFINE_CUSTOM_SCHEDULE_PROVIDER;
|
||||
};
|
||||
|
||||
|
||||
#endif // AI_BEHAVIOR_ALYX_INJURED_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,168 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AI_BEHAVIOR_PASSENGER_COMPANION_H
|
||||
#define AI_BEHAVIOR_PASSENGER_COMPANION_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "ai_behavior_passenger.h"
|
||||
|
||||
class CNPC_PlayerCompanion;
|
||||
|
||||
struct VehicleAvoidParams_t
|
||||
{
|
||||
Vector vecStartPos;
|
||||
Vector vecGoalPos;
|
||||
Vector *pNodePositions;
|
||||
int nNumNodes;
|
||||
int nDirection;
|
||||
int nStartNode;
|
||||
int nEndNode;
|
||||
};
|
||||
|
||||
struct FailPosition_t
|
||||
{
|
||||
Vector vecPosition;
|
||||
float flTime;
|
||||
|
||||
DECLARE_SIMPLE_DATADESC();
|
||||
};
|
||||
|
||||
class CAI_PassengerBehaviorCompanion : public CAI_PassengerBehavior
|
||||
{
|
||||
DECLARE_CLASS( CAI_PassengerBehaviorCompanion, CAI_PassengerBehavior );
|
||||
DECLARE_DATADESC()
|
||||
|
||||
public:
|
||||
|
||||
CAI_PassengerBehaviorCompanion( void );
|
||||
|
||||
enum
|
||||
{
|
||||
// Schedules
|
||||
SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE = BaseClass::NEXT_SCHEDULE,
|
||||
SCHED_PASSENGER_RUN_TO_ENTER_VEHICLE_FAILED,
|
||||
SCHED_PASSENGER_ENTER_VEHICLE_PAUSE,
|
||||
SCHED_PASSENGER_RANGE_ATTACK1,
|
||||
SCHED_PASSENGER_RELOAD,
|
||||
SCHED_PASSENGER_EXIT_STUCK_VEHICLE,
|
||||
SCHED_PASSENGER_OVERTURNED,
|
||||
SCHED_PASSENGER_IMPACT,
|
||||
SCHED_PASSENGER_ENTER_VEHICLE_IMMEDIATELY,
|
||||
SCHED_PASSENGER_COWER,
|
||||
SCHED_PASSENGER_FIDGET,
|
||||
NEXT_SCHEDULE,
|
||||
|
||||
// Tasks
|
||||
TASK_GET_PATH_TO_VEHICLE_ENTRY_POINT = BaseClass::NEXT_TASK,
|
||||
TASK_GET_PATH_TO_NEAR_VEHICLE,
|
||||
TASK_PASSENGER_RELOAD,
|
||||
TASK_PASSENGER_EXIT_STUCK_VEHICLE,
|
||||
TASK_PASSENGER_OVERTURNED,
|
||||
TASK_PASSENGER_IMPACT,
|
||||
TASK_RUN_TO_VEHICLE_ENTRANCE,
|
||||
NEXT_TASK,
|
||||
|
||||
// Conditions
|
||||
|
||||
COND_PASSENGER_CAN_LEAVE_STUCK_VEHICLE = BaseClass::NEXT_CONDITION,
|
||||
COND_PASSENGER_WARN_OVERTURNED,
|
||||
COND_PASSENGER_WARN_COLLISION,
|
||||
COND_PASSENGER_VEHICLE_MOVED_FROM_MARK,
|
||||
COND_PASSENGER_CAN_FIDGET,
|
||||
COND_PASSENGER_CAN_ENTER_IMMEDIATELY,
|
||||
NEXT_CONDITION,
|
||||
};
|
||||
|
||||
virtual bool CanSelectSchedule( void );
|
||||
virtual void Enable( CPropJeepEpisodic *pVehicle, bool bImmediateEnter = false);
|
||||
virtual void GatherConditions( void );
|
||||
virtual int SelectSchedule( void );
|
||||
virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
|
||||
virtual void StartTask( const Task_t *pTask );
|
||||
virtual void RunTask( const Task_t *pTask );
|
||||
virtual void AimGun( void );
|
||||
virtual void EnterVehicle( void );
|
||||
virtual void ExitVehicle( void );
|
||||
virtual void FinishEnterVehicle( void );
|
||||
virtual void FinishExitVehicle( void );
|
||||
virtual void BuildScheduleTestBits( void );
|
||||
virtual Activity NPC_TranslateActivity( Activity activity );
|
||||
virtual bool CanExitVehicle( void );
|
||||
virtual bool IsValidEnemy( CBaseEntity *pEntity );
|
||||
virtual void OnUpdateShotRegulator( void );
|
||||
virtual bool IsNavigationUrgent( void );
|
||||
virtual bool IsCurTaskContinuousMove( void );
|
||||
virtual bool IsCrouching( void );
|
||||
|
||||
private:
|
||||
|
||||
void SpeakVehicleConditions( void );
|
||||
virtual void OnExitVehicleFailed( void );
|
||||
|
||||
bool CanFidget( void );
|
||||
bool UseRadialRouteToEntryPoint( const Vector &vecEntryPoint );
|
||||
float GetArcToEntryPoint( const Vector &vecCenterPoint, const Vector &vecEntryPoint, bool &bClockwise );
|
||||
int SelectScheduleInsideVehicle( void );
|
||||
int SelectScheduleOutsideVehicle( void );
|
||||
bool FindPathToVehicleEntryPoint( void );
|
||||
bool CanEnterVehicleImmediately( int *pResultSequence, Vector *pResultPos, QAngle *pResultAngles );
|
||||
void EnterVehicleImmediately( void );
|
||||
|
||||
// ------------------------------------------
|
||||
// Passenger sensing
|
||||
// ------------------------------------------
|
||||
|
||||
virtual void GatherVehicleStateConditions( void );
|
||||
|
||||
float GetVehicleSpeed( void );
|
||||
void GatherVehicleCollisionConditions( const Vector &localVelocity );
|
||||
|
||||
// ------------------------------------------
|
||||
// Overturned tracking
|
||||
// ------------------------------------------
|
||||
void UpdateStuckStatus( void );
|
||||
bool CanExitAtPosition( const Vector &vecTestPos );
|
||||
bool GetStuckExitPos( Vector *vecResult );
|
||||
bool ExitStuckVehicle( void );
|
||||
|
||||
bool UpdateVehicleEntrancePath( void );
|
||||
bool PointIsWithinEntryFailureRadius( const Vector &vecPosition );
|
||||
void ResetVehicleEntryFailedState( void );
|
||||
void MarkVehicleEntryFailed( const Vector &vecPosition );
|
||||
virtual int FindEntrySequence( bool bNearest = false );
|
||||
void CalculateBodyLean( void );
|
||||
|
||||
float m_flNextJostleTime;
|
||||
float m_flNextOverturnWarning; // The next time the NPC may complained about being upside-down
|
||||
float m_flOverturnedDuration; // Amount of time we've been stuck in the vehicle (unable to exit)
|
||||
float m_flUnseenDuration; // Amount of time we've been hidden from the player's view
|
||||
|
||||
float m_flEnterBeginTime; // Time the NPC started to try and enter the vehicle
|
||||
int m_nExitAttempts; // Number of times we've attempted to exit the vehicle but failed
|
||||
int m_nVisibleEnemies; // Keeps a record of how many enemies I know about
|
||||
float m_flLastLateralLean; // Our last lean value
|
||||
|
||||
CAI_MoveMonitor m_VehicleMonitor; // Used to keep track of the vehicle's movement relative to a mark
|
||||
CUtlVector<FailPosition_t> m_FailedEntryPositions; // Used to keep track of the vehicle's movement relative to a mark
|
||||
|
||||
protected:
|
||||
virtual int SelectTransitionSchedule( void );
|
||||
|
||||
void ExtendFidgetDelay( float flDuration );
|
||||
bool CanPlayJostle( bool bLargeJostle );
|
||||
|
||||
float m_flEntraceUpdateTime;
|
||||
float m_flNextEnterAttempt;
|
||||
float m_flNextFidgetTime;
|
||||
CHandle< CNPC_PlayerCompanion > m_hCompanion;
|
||||
|
||||
DEFINE_CUSTOM_SCHEDULE_PROVIDER;
|
||||
};
|
||||
|
||||
#endif // AI_BEHAVIOR_PASSENGER_COMPANION_H
|
||||
@@ -0,0 +1,878 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Zombies on cars!
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "cbase.h"
|
||||
#include "npcevent.h"
|
||||
#include "ai_motor.h"
|
||||
#include "ai_senses.h"
|
||||
#include "vehicle_jeep_episodic.h"
|
||||
#include "npc_alyx_episodic.h"
|
||||
#include "ai_behavior_passenger_zombie.h"
|
||||
|
||||
#define JUMP_ATTACH_DIST_THRESHOLD 1000
|
||||
#define JUMP_ATTACH_FACING_THRESHOLD DOT_45DEGREE
|
||||
|
||||
#define ATTACH_PREDICTION_INTERVAL 0.2f
|
||||
#define ATTACH_PREDICTION_FACING_THRESHOLD 0.75f
|
||||
#define ATTACH_PREDICTION_DIST_THRESHOLD 128
|
||||
|
||||
int ACT_PASSENGER_MELEE_ATTACK1;
|
||||
int ACT_PASSENGER_THREATEN;
|
||||
int ACT_PASSENGER_FLINCH;
|
||||
int ACT_PASSENGER_ZOMBIE_LEAP_LOOP;
|
||||
|
||||
BEGIN_DATADESC( CAI_PassengerBehaviorZombie )
|
||||
|
||||
DEFINE_FIELD( m_flLastVerticalLean, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flLastLateralLean, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flNextLeapTime, FIELD_TIME ),
|
||||
|
||||
END_DATADESC();
|
||||
|
||||
extern int AE_PASSENGER_PHYSICS_PUSH;
|
||||
|
||||
//==============================================================================================
|
||||
// Passenger damage table
|
||||
//==============================================================================================
|
||||
static impactentry_t zombieLinearTable[] =
|
||||
{
|
||||
{ 200*200, 100 },
|
||||
};
|
||||
|
||||
static impactentry_t zombieAngularTable[] =
|
||||
{
|
||||
{ 100*100, 100 },
|
||||
};
|
||||
|
||||
impactdamagetable_t gZombiePassengerImpactDamageTable =
|
||||
{
|
||||
zombieLinearTable,
|
||||
zombieAngularTable,
|
||||
|
||||
ARRAYSIZE(zombieLinearTable),
|
||||
ARRAYSIZE(zombieAngularTable),
|
||||
|
||||
24*24, // minimum linear speed squared
|
||||
360*360, // minimum angular speed squared (360 deg/s to cause spin/slice damage)
|
||||
2, // can't take damage from anything under 2kg
|
||||
|
||||
5, // anything less than 5kg is "small"
|
||||
5, // never take more than 5 pts of damage from anything under 5kg
|
||||
36*36, // <5kg objects must go faster than 36 in/s to do damage
|
||||
|
||||
VPHYSICS_LARGE_OBJECT_MASS, // large mass in kg
|
||||
4, // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
|
||||
5, // large mass falling scale (emphasize falling/crushing damage over sideways impacts since the stress will kill you anyway)
|
||||
0.0f, // min vel
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CAI_PassengerBehaviorZombie::CAI_PassengerBehaviorZombie( void ) :
|
||||
m_flLastVerticalLean( 0.0f ),
|
||||
m_flLastLateralLean( 0.0f ),
|
||||
m_flNextLeapTime( 0.0f )
|
||||
{
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_PassengerBehaviorZombie::CanEnterVehicle( void )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Translate into vehicle passengers
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_PassengerBehaviorZombie::TranslateSchedule( int scheduleType )
|
||||
{
|
||||
// We do different animations when inside the vehicle
|
||||
if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
|
||||
{
|
||||
if ( scheduleType == SCHED_MELEE_ATTACK1 )
|
||||
return SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1;
|
||||
|
||||
if ( scheduleType == SCHED_RANGE_ATTACK1 )
|
||||
return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
|
||||
}
|
||||
|
||||
return BaseClass::TranslateSchedule( scheduleType );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : activity -
|
||||
// Output : Activity
|
||||
//-----------------------------------------------------------------------------
|
||||
Activity CAI_PassengerBehaviorZombie::NPC_TranslateActivity( Activity activity )
|
||||
{
|
||||
Activity nNewActivity = BaseClass::NPC_TranslateActivity( activity );
|
||||
if ( activity == ACT_IDLE )
|
||||
return (Activity) ACT_PASSENGER_IDLE;
|
||||
|
||||
return nNewActivity;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Suppress melee attacks against enemies for the given duration
|
||||
// Input : flDuration - Amount of time to suppress the attacks
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::SuppressAttack( float flDuration )
|
||||
{
|
||||
GetOuter()->SetNextAttack( gpGlobals->curtime + flDuration );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Determines if an enemy is inside a vehicle or not
|
||||
// Output : Returns true if the enemy is outside the vehicle.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_PassengerBehaviorZombie::EnemyInVehicle( void )
|
||||
{
|
||||
// Obviously they're not...
|
||||
if ( GetOuter()->GetEnemy() == NULL )
|
||||
return false;
|
||||
|
||||
// See if they're in a vehicle, currently
|
||||
CBaseCombatCharacter *pCCEnemy = GetOuter()->GetEnemy()->MyCombatCharacterPointer();
|
||||
if ( pCCEnemy && pCCEnemy->IsInAVehicle() )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Select a schedule when we're outside of the vehicle
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_PassengerBehaviorZombie::SelectOutsideSchedule( void )
|
||||
{
|
||||
// Attaching to target
|
||||
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
|
||||
return SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1;
|
||||
|
||||
// Attack the player if we're able
|
||||
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
|
||||
return SCHED_MELEE_ATTACK1;
|
||||
|
||||
// Attach to the vehicle
|
||||
if ( HasCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE ) )
|
||||
return SCHED_PASSENGER_ZOMBIE_ATTACH;
|
||||
|
||||
// Otherwise chase after him
|
||||
return SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Pick a schedule for being "inside" the vehicle
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_PassengerBehaviorZombie::SelectInsideSchedule( void )
|
||||
{
|
||||
// Attacking target
|
||||
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
|
||||
return SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1;
|
||||
|
||||
return SCHED_IDLE_STAND;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Move the zombie to the vehicle
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_PassengerBehaviorZombie::SelectSchedule( void )
|
||||
{
|
||||
// See if our enemy got out
|
||||
if ( GetOuter()->GetEnemy() != NULL && EnemyInVehicle() == false )
|
||||
{
|
||||
if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
|
||||
{
|
||||
// Exit the vehicle
|
||||
SetCondition( COND_PASSENGER_EXITING );
|
||||
}
|
||||
else if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
|
||||
{
|
||||
// Our target has left the vehicle and we're outside as well, so give up
|
||||
Disable();
|
||||
return BaseClass::SelectSchedule();
|
||||
}
|
||||
}
|
||||
|
||||
// Entering schedule
|
||||
if ( HasCondition( COND_PASSENGER_ENTERING ) )
|
||||
{
|
||||
ClearCondition( COND_PASSENGER_ENTERING );
|
||||
return SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE;
|
||||
}
|
||||
|
||||
// Exiting schedule
|
||||
if ( HasCondition( COND_PASSENGER_EXITING ) )
|
||||
{
|
||||
ClearCondition( COND_PASSENGER_EXITING );
|
||||
return SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE;
|
||||
}
|
||||
|
||||
// Select different schedules based on our state
|
||||
PassengerState_e nState = GetPassengerState();
|
||||
int nNewSchedule = SCHED_NONE;
|
||||
|
||||
if ( nState == PASSENGER_STATE_INSIDE )
|
||||
{
|
||||
nNewSchedule = SelectInsideSchedule();
|
||||
if ( nNewSchedule != SCHED_NONE )
|
||||
return nNewSchedule;
|
||||
}
|
||||
else if ( nState == PASSENGER_STATE_OUTSIDE )
|
||||
{
|
||||
nNewSchedule = SelectOutsideSchedule();
|
||||
if ( nNewSchedule != SCHED_NONE )
|
||||
return nNewSchedule;
|
||||
}
|
||||
|
||||
// Worst case he just stands here
|
||||
Assert(0);
|
||||
return SCHED_IDLE_STAND;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_PassengerBehaviorZombie::CanJumpToAttachToVehicle( void )
|
||||
{
|
||||
// FIXME: Probably move this up one level and out of this function
|
||||
if ( m_flNextLeapTime > gpGlobals->curtime )
|
||||
return false;
|
||||
|
||||
// Predict an attachment jump
|
||||
CBaseEntity *pEnemy = GetOuter()->GetEnemy();
|
||||
|
||||
Vector vecPredictedPosition;
|
||||
UTIL_PredictedPosition( pEnemy, 1.0f, &vecPredictedPosition );
|
||||
|
||||
float flDist = UTIL_DistApprox( vecPredictedPosition, GetOuter()->GetAbsOrigin() );
|
||||
|
||||
// If we're facing them enough, allow the jump
|
||||
if ( ( flDist < JUMP_ATTACH_DIST_THRESHOLD ) && UTIL_IsFacingWithinTolerance( GetOuter(), pEnemy, JUMP_ATTACH_FACING_THRESHOLD ) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Determine if we can jump to be on the enemy's vehicle
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
inline bool CAI_PassengerBehaviorZombie::CanBeOnEnemyVehicle( void )
|
||||
{
|
||||
CBaseCombatCharacter *pEnemy = ToBaseCombatCharacter( GetOuter()->GetEnemy() );
|
||||
if ( pEnemy != NULL )
|
||||
{
|
||||
IServerVehicle *pVehicle = pEnemy->GetVehicle();
|
||||
if ( pVehicle && pVehicle->NPC_HasAvailableSeat( GetRoleName() ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::GatherConditions( void )
|
||||
{
|
||||
BaseClass::GatherConditions();
|
||||
|
||||
// Always clear the base conditions
|
||||
ClearCondition( COND_CAN_MELEE_ATTACK1 );
|
||||
|
||||
// Behavior when outside the vehicle
|
||||
if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
|
||||
{
|
||||
if ( CanBeOnEnemyVehicle() && CanJumpToAttachToVehicle() )
|
||||
{
|
||||
SetCondition( COND_CAN_RANGE_ATTACK1 );
|
||||
}
|
||||
|
||||
// Determine if we can latch on to the vehicle (out of sight)
|
||||
ClearCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE );
|
||||
CBasePlayer *pPlayer = AI_GetSinglePlayer();
|
||||
|
||||
if ( pPlayer != NULL &&
|
||||
GetOuter()->GetEnemy() == pPlayer &&
|
||||
pPlayer->GetVehicleEntity() == m_hVehicle )
|
||||
{
|
||||
// Can't be visible to the player and must be close enough
|
||||
bool bNotVisibleToPlayer = ( pPlayer->FInViewCone( GetOuter() ) == false );
|
||||
float flDistSqr = ( pPlayer->GetAbsOrigin() - GetOuter()->GetAbsOrigin() ).LengthSqr();
|
||||
bool bInRange = ( flDistSqr < Square(250.0f) );
|
||||
if ( bNotVisibleToPlayer && bInRange )
|
||||
{
|
||||
// We can latch on and "enter" the vehicle
|
||||
SetCondition( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE );
|
||||
}
|
||||
else if ( bNotVisibleToPlayer == false && flDistSqr < Square(128.0f) )
|
||||
{
|
||||
// Otherwise just hit the vehicle in anger
|
||||
SetCondition( COND_CAN_MELEE_ATTACK1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Behavior when on the car
|
||||
if ( GetPassengerState() == PASSENGER_STATE_INSIDE )
|
||||
{
|
||||
// Check for melee attack
|
||||
if ( GetOuter()->GetNextAttack() < gpGlobals->curtime )
|
||||
{
|
||||
SetCondition( COND_CAN_MELEE_ATTACK1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Handle death case
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::Event_Killed( const CTakeDamageInfo &info )
|
||||
{
|
||||
if ( m_hVehicle )
|
||||
{
|
||||
// Stop taking messages from the vehicle
|
||||
m_hVehicle->RemovePhysicsChild( GetOuter() );
|
||||
m_hVehicle->NPC_RemovePassenger( GetOuter() );
|
||||
m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
|
||||
}
|
||||
|
||||
BaseClass::Event_Killed( info );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Build our custom interrupt cases for the behavior
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::BuildScheduleTestBits( void )
|
||||
{
|
||||
// Always interrupt when we need to get in or out
|
||||
if ( GetPassengerState() == PASSENGER_STATE_OUTSIDE )
|
||||
{
|
||||
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_CAN_RANGE_ATTACK1 ) );
|
||||
GetOuter()->SetCustomInterruptCondition( GetClassScheduleIdSpace()->ConditionLocalToGlobal( COND_PASSENGER_ENTERING ) );
|
||||
}
|
||||
|
||||
BaseClass::BuildScheduleTestBits();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Get the absolute position of the desired attachment point
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::GetAttachmentPoint( Vector *vecPoint )
|
||||
{
|
||||
Vector vecEntryOffset, vecFinalOffset;
|
||||
GetEntryTarget( &vecEntryOffset, NULL );
|
||||
VectorRotate( vecEntryOffset, m_hVehicle->GetAbsAngles(), vecFinalOffset );
|
||||
*vecPoint = ( m_hVehicle->GetAbsOrigin() + vecFinalOffset );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_PassengerBehaviorZombie::FindExitSequence( void )
|
||||
{
|
||||
// Get a list of all our animations
|
||||
const PassengerSeatAnims_t *pExitAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_EXIT );
|
||||
if ( pExitAnims == NULL )
|
||||
return -1;
|
||||
|
||||
// Test each animation (sorted by priority) for the best match
|
||||
for ( int i = 0; i < pExitAnims->Count(); i++ )
|
||||
{
|
||||
// Find the activity for this animation name
|
||||
int nSequence = GetOuter()->LookupSequence( STRING( pExitAnims->Element(i).GetAnimationName() ) );
|
||||
Assert( nSequence != -1 );
|
||||
if ( nSequence == -1 )
|
||||
continue;
|
||||
|
||||
return nSequence;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::StartDismount( void )
|
||||
{
|
||||
// Leap off the vehicle
|
||||
int nSequence = FindExitSequence();
|
||||
Assert( nSequence != -1 );
|
||||
|
||||
SetTransitionSequence( nSequence );
|
||||
GetOuter()->SetIdealActivity( ACT_SCRIPT_CUSTOM_MOVE );
|
||||
|
||||
// This removes the NPC from the vehicle's handling and fires all necessary outputs
|
||||
m_hVehicle->RemovePhysicsChild( GetOuter() );
|
||||
m_hVehicle->NPC_RemovePassenger( GetOuter() );
|
||||
m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), (IsPassengerHostile()==false) );
|
||||
|
||||
// Detach from the parent
|
||||
GetOuter()->SetParent( NULL );
|
||||
GetOuter()->SetMoveType( MOVETYPE_STEP );
|
||||
GetMotor()->SetYawLocked( false );
|
||||
|
||||
QAngle vecAngles = GetAbsAngles();
|
||||
vecAngles.z = 0.0f;
|
||||
GetOuter()->SetAbsAngles( vecAngles );
|
||||
|
||||
// HACK: Will this work?
|
||||
IPhysicsObject *pPhysObj = GetOuter()->VPhysicsGetObject();
|
||||
if ( pPhysObj != NULL )
|
||||
{
|
||||
pPhysObj->EnableCollisions( true );
|
||||
}
|
||||
|
||||
// Clear this
|
||||
m_PassengerIntent = PASSENGER_INTENT_NONE;
|
||||
SetPassengerState( PASSENGER_STATE_EXITING );
|
||||
|
||||
// Get the velocity
|
||||
Vector vecUp, vecJumpDir;
|
||||
GetOuter()->GetVectors( &vecJumpDir, NULL, &vecUp );
|
||||
|
||||
// Move back and up
|
||||
vecJumpDir *= random->RandomFloat( -400.0f, -500.0f );
|
||||
vecJumpDir += vecUp * 150.0f;
|
||||
GetOuter()->SetAbsVelocity( vecJumpDir );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::FinishDismount( void )
|
||||
{
|
||||
SetPassengerState( PASSENGER_STATE_OUTSIDE );
|
||||
Disable();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::StartTask( const Task_t *pTask )
|
||||
{
|
||||
switch ( pTask->iTask )
|
||||
{
|
||||
case TASK_FACE_HINTNODE:
|
||||
case TASK_FACE_LASTPOSITION:
|
||||
case TASK_FACE_SAVEPOSITION:
|
||||
case TASK_FACE_TARGET:
|
||||
case TASK_FACE_IDEAL:
|
||||
case TASK_FACE_SCRIPT:
|
||||
case TASK_FACE_PATH:
|
||||
TaskComplete();
|
||||
break;
|
||||
|
||||
case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
|
||||
break;
|
||||
|
||||
case TASK_MELEE_ATTACK1:
|
||||
{
|
||||
// Only override this if we're "in" the vehicle
|
||||
if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
|
||||
{
|
||||
BaseClass::StartTask( pTask );
|
||||
break;
|
||||
}
|
||||
|
||||
// Swipe
|
||||
GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_MELEE_ATTACK1 );
|
||||
|
||||
// Randomly attack again in the future
|
||||
float flWait = random->RandomFloat( 0.0f, 1.0f );
|
||||
SuppressAttack( flWait );
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_PASSENGER_ZOMBIE_DISMOUNT:
|
||||
{
|
||||
// Start the process of dismounting from the vehicle
|
||||
StartDismount();
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_PASSENGER_ZOMBIE_ATTACH:
|
||||
{
|
||||
if ( AttachToVehicle() )
|
||||
{
|
||||
TaskComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
TaskFail( "Unable to attach to vehicle!" );
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
BaseClass::StartTask( pTask );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Handle task running
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::RunTask( const Task_t *pTask )
|
||||
{
|
||||
switch ( pTask->iTask )
|
||||
{
|
||||
case TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1:
|
||||
{
|
||||
// Face the entry point
|
||||
Vector vecAttachPoint;
|
||||
GetAttachmentPoint( &vecAttachPoint );
|
||||
GetOuter()->GetMotor()->SetIdealYawToTarget( vecAttachPoint );
|
||||
|
||||
// All done when you touch the ground
|
||||
if ( GetOuter()->GetFlags() & FL_ONGROUND )
|
||||
{
|
||||
m_flNextLeapTime = gpGlobals->curtime + 2.0f;
|
||||
TaskComplete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TASK_MELEE_ATTACK1:
|
||||
|
||||
if ( GetOuter()->IsSequenceFinished() )
|
||||
{
|
||||
TaskComplete();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TASK_PASSENGER_ZOMBIE_DISMOUNT:
|
||||
{
|
||||
if ( GetOuter()->IsSequenceFinished() )
|
||||
{
|
||||
// Completely separate from the vehicle
|
||||
FinishDismount();
|
||||
TaskComplete();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
BaseClass::RunTask( pTask );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Find the relative cost of an entry point based on facing
|
||||
// Input : &vecEntryPos - Position we're evaluating
|
||||
// Output : Returns the cost as a modified distance value
|
||||
//-----------------------------------------------------------------------------
|
||||
float CAI_PassengerBehaviorZombie::GetEntryPointCost( const Vector &vecEntryPos )
|
||||
{
|
||||
// FIXME: We don't care about cost any longer!
|
||||
return 1.0f;
|
||||
|
||||
// Find the direction from us to the entry point
|
||||
Vector vecEntryDir = ( vecEntryPos - GetAbsOrigin() );
|
||||
float flCost = VectorNormalize( vecEntryDir );
|
||||
|
||||
// Get our current facing
|
||||
Vector vecDir;
|
||||
GetOuter()->GetVectors( &vecDir, NULL, NULL );
|
||||
|
||||
// Scale our cost by how closely it matches our facing
|
||||
float flDot = DotProduct( vecEntryDir, vecDir );
|
||||
if ( flDot < 0.0f )
|
||||
return FLT_MAX;
|
||||
|
||||
flCost *= RemapValClamped( flDot, 1.0f, 0.0f, 1.0f, 2.0f );
|
||||
|
||||
return flCost;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : bNearest -
|
||||
// Output : int
|
||||
//-----------------------------------------------------------------------------
|
||||
int CAI_PassengerBehaviorZombie::FindEntrySequence( bool bNearest /*= false*/ )
|
||||
{
|
||||
// Get a list of all our animations
|
||||
const PassengerSeatAnims_t *pEntryAnims = m_hVehicle->GetServerVehicle()->NPC_GetPassengerSeatAnims( GetOuter(), PASSENGER_SEAT_ENTRY );
|
||||
if ( pEntryAnims == NULL )
|
||||
return -1;
|
||||
|
||||
Vector vecStartPos;
|
||||
const CPassengerSeatTransition *pTransition;
|
||||
float flBestCost = FLT_MAX;
|
||||
float flCost;
|
||||
int nBestSequence = -1;
|
||||
int nSequence = -1;
|
||||
|
||||
// Test each animation (sorted by priority) for the best match
|
||||
for ( int i = 0; i < pEntryAnims->Count(); i++ )
|
||||
{
|
||||
// Find the activity for this animation name
|
||||
pTransition = &pEntryAnims->Element(i);
|
||||
nSequence = GetOuter()->LookupSequence( STRING( pTransition->GetAnimationName() ) );
|
||||
|
||||
Assert( nSequence != -1 );
|
||||
if ( nSequence == -1 )
|
||||
continue;
|
||||
|
||||
// Test this entry for validity
|
||||
GetEntryPoint( nSequence, &vecStartPos );
|
||||
|
||||
// Evaluate the cost
|
||||
flCost = GetEntryPointCost( vecStartPos );
|
||||
if ( flCost < flBestCost )
|
||||
{
|
||||
nBestSequence = nSequence;
|
||||
flBestCost = flCost;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return nBestSequence;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::ExitVehicle( void )
|
||||
{
|
||||
BaseClass::ExitVehicle();
|
||||
|
||||
// Remove us as a passenger
|
||||
m_hVehicle->NPC_RemovePassenger( GetOuter() );
|
||||
m_hVehicle->NPC_FinishedExitVehicle( GetOuter(), false );
|
||||
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Calculate our body lean based on our delta velocity
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::CalculateBodyLean( void )
|
||||
{
|
||||
// Calculate our lateral displacement from a perfectly centered start
|
||||
float flLateralDisp = SimpleSplineRemapVal( m_vehicleState.m_vecLastAngles.z, 100.0f, -100.0f, -1.0f, 1.0f );
|
||||
flLateralDisp = clamp( flLateralDisp, -1.0f, 1.0f );
|
||||
|
||||
// FIXME: Framerate dependant!
|
||||
m_flLastLateralLean = ( m_flLastLateralLean * 0.2f ) + ( flLateralDisp * 0.8f );
|
||||
|
||||
// Factor in a "stun" if the zombie was moved too far off course
|
||||
if ( fabs( m_flLastLateralLean ) > 0.75f )
|
||||
{
|
||||
SuppressAttack( 0.5f );
|
||||
}
|
||||
|
||||
// Calc our vertical displacement
|
||||
float flVerticalDisp = SimpleSplineRemapVal( m_vehicleState.m_vecDeltaVelocity.z, -50.0f, 50.0f, -1.0f, 1.0f );
|
||||
flVerticalDisp = clamp( flVerticalDisp, -1.0f, 1.0f );
|
||||
|
||||
// FIXME: Framerate dependant!
|
||||
m_flLastVerticalLean = ( m_flLastVerticalLean * 0.75f ) + ( flVerticalDisp * 0.25f );
|
||||
|
||||
// Set these parameters
|
||||
GetOuter()->SetPoseParameter( "lean_lateral", m_flLastLateralLean );
|
||||
GetOuter()->SetPoseParameter( "lean_vertical", m_flLastVerticalLean );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::GatherVehicleStateConditions( void )
|
||||
{
|
||||
// Call to the base
|
||||
BaseClass::GatherVehicleStateConditions();
|
||||
|
||||
// Only do this if we're on the vehicle
|
||||
if ( GetPassengerState() != PASSENGER_STATE_INSIDE )
|
||||
return;
|
||||
|
||||
// Calculate how our body is leaning
|
||||
CalculateBodyLean();
|
||||
|
||||
// The forward delta of the vehicle
|
||||
float flLateralDelta = ( m_vehicleState.m_vecDeltaVelocity.x + m_vehicleState.m_vecDeltaVelocity.y );
|
||||
|
||||
// Detect a sudden stop
|
||||
if ( flLateralDelta < -350.0f )
|
||||
{
|
||||
if ( m_hVehicle )
|
||||
{
|
||||
Vector vecDamageForce;
|
||||
m_hVehicle->GetVelocity( &vecDamageForce, NULL );
|
||||
VectorNormalize( vecDamageForce );
|
||||
vecDamageForce *= random->RandomFloat( 50000.0f, 60000.0f );
|
||||
|
||||
//NDebugOverlay::HorzArrow( GetAbsOrigin(), GetAbsOrigin() + ( vecDamageForce * 256.0f ), 16.0f, 255, 0, 0, 16, true, 2.0f );
|
||||
|
||||
// Fake it!
|
||||
CTakeDamageInfo info( m_hVehicle, m_hVehicle, vecDamageForce, GetOuter()->WorldSpaceCenter(), 200, (DMG_CRUSH|DMG_VEHICLE) );
|
||||
GetOuter()->TakeDamage( info );
|
||||
}
|
||||
}
|
||||
else if ( flLateralDelta < -150.0f )
|
||||
{
|
||||
// FIXME: Realistically this should interrupt and play a schedule to do it
|
||||
GetOuter()->SetIdealActivity( (Activity) ACT_PASSENGER_FLINCH );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *pEvent -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CAI_PassengerBehaviorZombie::HandleAnimEvent( animevent_t *pEvent )
|
||||
{
|
||||
if ( pEvent->event == AE_PASSENGER_PHYSICS_PUSH )
|
||||
{
|
||||
// Add a push into the vehicle
|
||||
float flForce = (float) atof( pEvent->options );
|
||||
AddPhysicsPush( flForce * 0.75f );
|
||||
return;
|
||||
}
|
||||
|
||||
BaseClass::HandleAnimEvent( pEvent );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Attach to the vehicle if we're able
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CAI_PassengerBehaviorZombie::AttachToVehicle( void )
|
||||
{
|
||||
// Must be able to enter the vehicle
|
||||
if ( m_hVehicle->NPC_CanEnterVehicle( GetOuter(), false ) == false )
|
||||
return false;
|
||||
|
||||
// Reserve the seat
|
||||
if ( ReserveEntryPoint( VEHICLE_SEAT_ANY ) == false )
|
||||
return false;
|
||||
|
||||
// Use the best one we've found
|
||||
int nSequence = FindEntrySequence();
|
||||
if ( nSequence == -1 )
|
||||
return false;
|
||||
|
||||
// Take the transition sequence
|
||||
SetTransitionSequence( nSequence );
|
||||
|
||||
// Get in the vehicle
|
||||
EnterVehicle();
|
||||
|
||||
// Start our scripted sequence with any other passengers
|
||||
// Find Alyx
|
||||
// TODO: Iterate through the list of passengers in the vehicle and find one we can interact with
|
||||
CNPC_Alyx *pAlyx = CNPC_Alyx::GetAlyx();
|
||||
if ( pAlyx )
|
||||
{
|
||||
// Tell Alyx to play along!
|
||||
pAlyx->ForceVehicleInteraction( GetOuter()->GetSequenceName( nSequence ), GetOuter() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_PassengerBehaviorZombie )
|
||||
{
|
||||
DECLARE_ACTIVITY( ACT_PASSENGER_MELEE_ATTACK1 )
|
||||
DECLARE_ACTIVITY( ACT_PASSENGER_THREATEN )
|
||||
DECLARE_ACTIVITY( ACT_PASSENGER_FLINCH )
|
||||
DECLARE_ACTIVITY( ACT_PASSENGER_ZOMBIE_LEAP_LOOP )
|
||||
|
||||
DECLARE_TASK( TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 )
|
||||
DECLARE_TASK( TASK_PASSENGER_ZOMBIE_DISMOUNT )
|
||||
DECLARE_TASK( TASK_PASSENGER_ZOMBIE_ATTACH )
|
||||
|
||||
DECLARE_CONDITION( COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE )
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE,
|
||||
|
||||
" Tasks"
|
||||
" TASK_PASSENGER_ATTACH_TO_VEHICLE 0"
|
||||
" TASK_PASSENGER_ENTER_VEHICLE 0"
|
||||
""
|
||||
" Interrupts"
|
||||
)
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE,
|
||||
|
||||
" Tasks"
|
||||
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_PASSENGER_IDLE"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_PASSENGER_ZOMBIE_DISMOUNT 0"
|
||||
""
|
||||
" Interrupts"
|
||||
" COND_TASK_FAILED"
|
||||
)
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1,
|
||||
|
||||
" Tasks"
|
||||
" TASK_ANNOUNCE_ATTACK 1"
|
||||
" TASK_MELEE_ATTACK1 0"
|
||||
""
|
||||
" Interrupts"
|
||||
)
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1,
|
||||
|
||||
" Tasks"
|
||||
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_PASSENGER_RANGE_ATTACK1"
|
||||
" TASK_SET_ACTIVITY ACTIVITY:ACT_PASSENGER_ZOMBIE_LEAP_LOOP"
|
||||
" TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 0"
|
||||
" "
|
||||
" Interrupts"
|
||||
)
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE,
|
||||
|
||||
" Tasks"
|
||||
" TASK_STOP_MOVING 0"
|
||||
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
|
||||
" TASK_GET_CHASE_PATH_TO_ENEMY 2400"
|
||||
" TASK_RUN_PATH 0"
|
||||
" TASK_WAIT_FOR_MOVEMENT 0"
|
||||
""
|
||||
" Interrupts"
|
||||
" COND_NEW_ENEMY"
|
||||
" COND_ENEMY_DEAD"
|
||||
" COND_ENEMY_UNREACHABLE"
|
||||
" COND_TASK_FAILED"
|
||||
" COND_LOST_ENEMY"
|
||||
" COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE"
|
||||
)
|
||||
|
||||
DEFINE_SCHEDULE
|
||||
(
|
||||
SCHED_PASSENGER_ZOMBIE_ATTACH,
|
||||
|
||||
" Tasks"
|
||||
" TASK_PASSENGER_ZOMBIE_ATTACH 0"
|
||||
""
|
||||
" Interrupts"
|
||||
)
|
||||
|
||||
AI_END_CUSTOM_SCHEDULE_PROVIDER()
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Zombies on cars!
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AI_BEHAVIOR_PASSENGER_ZOMBIE_H
|
||||
#define AI_BEHAVIOR_PASSENGER_ZOMBIE_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "ai_behavior_passenger.h"
|
||||
#include "ai_utils.h"
|
||||
#include "vehicle_base.h"
|
||||
|
||||
extern impactdamagetable_t gZombiePassengerImpactDamageTable;
|
||||
|
||||
class CAI_PassengerBehaviorZombie : public CAI_PassengerBehavior
|
||||
{
|
||||
DECLARE_CLASS( CAI_PassengerBehaviorZombie, CAI_PassengerBehavior );
|
||||
DECLARE_DATADESC()
|
||||
|
||||
public:
|
||||
|
||||
CAI_PassengerBehaviorZombie( void );
|
||||
|
||||
enum
|
||||
{
|
||||
// Schedules
|
||||
SCHED_PASSENGER_ZOMBIE_ENTER_VEHICLE = BaseClass::NEXT_SCHEDULE,
|
||||
SCHED_PASSENGER_ZOMBIE_EXIT_VEHICLE,
|
||||
SCHED_PASSENGER_ZOMBIE_MELEE_ATTACK1,
|
||||
SCHED_PASSENGER_ZOMBIE_RANGE_ATTACK1,
|
||||
SCHED_PASSENGER_ZOMBIE_ATTACH,
|
||||
SCHED_PASSENGER_ZOMBIE_RUN_TO_VEHICLE,
|
||||
NEXT_SCHEDULE,
|
||||
|
||||
// Tasks
|
||||
TASK_PASSENGER_ZOMBIE_RANGE_ATTACK1 = BaseClass::NEXT_TASK,
|
||||
TASK_PASSENGER_ZOMBIE_DISMOUNT,
|
||||
TASK_PASSENGER_ZOMBIE_ATTACH,
|
||||
NEXT_TASK,
|
||||
|
||||
// Conditions
|
||||
COND_PASSENGER_ZOMBIE_CAN_ATTACH_TO_VEHICLE = BaseClass::NEXT_CONDITION,
|
||||
NEXT_CONDITION
|
||||
};
|
||||
|
||||
virtual const char *GetName( void ) { return "ZombiePassenger"; }
|
||||
virtual string_t GetRoleName( void ) { return MAKE_STRING( "passenger_zombie" ); }
|
||||
virtual int SelectSchedule( void );
|
||||
virtual int TranslateSchedule( int scheduleType );
|
||||
virtual void GatherConditions( void );
|
||||
virtual void Event_Killed( const CTakeDamageInfo &info );
|
||||
virtual void BuildScheduleTestBits( void );
|
||||
virtual void RunTask( const Task_t *pTask );
|
||||
virtual void StartTask( const Task_t *pTask );
|
||||
virtual bool CanEnterVehicle( void );
|
||||
virtual void ExitVehicle( void );
|
||||
virtual void HandleAnimEvent( animevent_t *pEvent );
|
||||
virtual Activity NPC_TranslateActivity( Activity activity );
|
||||
|
||||
virtual bool AttachToVehicle( void );
|
||||
|
||||
void SuppressAttack( float flDuration );
|
||||
|
||||
DEFINE_CUSTOM_SCHEDULE_PROVIDER;
|
||||
|
||||
protected:
|
||||
|
||||
int SelectOutsideSchedule( void );
|
||||
int SelectInsideSchedule( void );
|
||||
virtual int FindExitSequence( void );
|
||||
void StartDismount( void );
|
||||
void FinishDismount( void );
|
||||
virtual void CalculateBodyLean( void );
|
||||
virtual void GatherVehicleStateConditions( void );
|
||||
virtual int FindEntrySequence( bool bNearest = false );
|
||||
|
||||
private:
|
||||
|
||||
void VehicleLeapAttackTouch( CBaseEntity *pOther );
|
||||
void VehicleLeapAttack( void );
|
||||
bool CanBeOnEnemyVehicle( void );
|
||||
float GetEntryPointCost( const Vector &vecEntryPos );
|
||||
bool EnemyInVehicle( void );
|
||||
void GetAttachmentPoint( Vector *vecPoint );
|
||||
bool CanJumpToAttachToVehicle( void );
|
||||
//bool WithinAttachRange( void );
|
||||
|
||||
float m_flLastLateralLean;
|
||||
float m_flLastVerticalLean;
|
||||
float m_flNextLeapTime;
|
||||
};
|
||||
|
||||
#endif // AI_BEHAVIOR_PASSENGER_ZOMBIE_H
|
||||
@@ -0,0 +1,74 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
//Gamestats was built for ep1, so this file is going to be amazingly short seeing as how ep1 set the standard
|
||||
|
||||
#include "cbase.h"
|
||||
#include "ep1_gamestats.h"
|
||||
#include "tier1/utlbuffer.h"
|
||||
static CEP1GameStats s_CEP1GS_ThisJustSitsInMemory;
|
||||
|
||||
// A bit of a hack to redirect the gamestats API for ep2 (ep3, etc.)
|
||||
extern CBaseGameStats *g_pEP2GameStats;
|
||||
|
||||
CEP1GameStats::CEP1GameStats( void )
|
||||
{
|
||||
gamestats = &s_CEP1GS_ThisJustSitsInMemory;
|
||||
}
|
||||
|
||||
CBaseGameStats *CEP1GameStats::OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir )
|
||||
{
|
||||
if ( !Q_stricmp( gamedir, "ep2" ) )
|
||||
{
|
||||
return g_pEP2GameStats;
|
||||
}
|
||||
|
||||
return pCurrentGameStats;
|
||||
}
|
||||
|
||||
const char *CEP1GameStats::GetStatSaveFileName( void )
|
||||
{
|
||||
return "ep1_gamestats.dat"; //overriding the default for backwards compatibility with release stat tracking code
|
||||
}
|
||||
|
||||
const char *CEP1GameStats::GetStatUploadRegistryKeyName( void )
|
||||
{
|
||||
return "GameStatsUpload_Ep1"; //overriding the default for backwards compatibility with release stat tracking code
|
||||
}
|
||||
|
||||
|
||||
static char const *ep1Maps[] =
|
||||
{
|
||||
"ep1_citadel_00",
|
||||
"ep1_citadel_01",
|
||||
"ep1_citadel_02",
|
||||
"ep1_citadel_02b",
|
||||
"ep1_citadel_03",
|
||||
"ep1_citadel_04",
|
||||
"ep1_c17_00",
|
||||
"ep1_c17_00a",
|
||||
"ep1_c17_01",
|
||||
"ep1_c17_02",
|
||||
"ep1_c17_02b",
|
||||
"ep1_c17_02a",
|
||||
"ep1_c17_05",
|
||||
"ep1_c17_06",
|
||||
};
|
||||
|
||||
|
||||
bool CEP1GameStats::UserPlayedAllTheMaps( void )
|
||||
{
|
||||
int c = ARRAYSIZE( ep1Maps );
|
||||
for ( int i = 0; i < c; ++i )
|
||||
{
|
||||
int idx = m_BasicStats.m_MapTotals.Find( ep1Maps[ i ] );
|
||||
if( idx == m_BasicStats.m_MapTotals.InvalidIndex() )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef EP1_GAMESTATS_H
|
||||
#define EP1_GAMESTATS_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "gamestats.h"
|
||||
|
||||
class CEP1GameStats : public CBaseGameStats
|
||||
{
|
||||
typedef CBaseGameStats BaseClass;
|
||||
|
||||
public:
|
||||
CEP1GameStats( void );
|
||||
|
||||
virtual CBaseGameStats *OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir );
|
||||
|
||||
virtual bool StatTrackingEnabledForMod( void ) { return true; }
|
||||
virtual bool UserPlayedAllTheMaps( void );
|
||||
|
||||
virtual const char *GetStatSaveFileName( void );
|
||||
virtual const char *GetStatUploadRegistryKeyName( void );
|
||||
};
|
||||
|
||||
#endif // EP1_GAMESTATS_H
|
||||
@@ -0,0 +1,585 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
#if defined( GAME_DLL )
|
||||
#include "cbase.h"
|
||||
#endif
|
||||
#include "ep2_gamestats.h"
|
||||
#include "tier1/utlbuffer.h"
|
||||
#include "vehicle_base.h"
|
||||
#include "tier1/utlstring.h"
|
||||
#include "filesystem.h"
|
||||
#include "icommandline.h"
|
||||
|
||||
static CEP2GameStats s_CEP2GameStats_Singleton;
|
||||
CBaseGameStats *g_pEP2GameStats = &s_CEP2GameStats_Singleton;
|
||||
|
||||
|
||||
CEP2GameStats::CEP2GameStats( void )
|
||||
{
|
||||
Q_memset( m_flInchesRemainder, 0, sizeof( m_flInchesRemainder ) );
|
||||
m_pCurrentMap = NULL;
|
||||
m_dictMapStats.Purge();
|
||||
}
|
||||
|
||||
const char *CEP2GameStats::GetStatSaveFileName( void )
|
||||
{
|
||||
//overriding the default for backwards compatibility with release stat tracking code
|
||||
return "ep2_gamestats.dat";
|
||||
}
|
||||
|
||||
const char *CEP2GameStats::GetStatUploadRegistryKeyName( void )
|
||||
{
|
||||
//overriding the default for backwards compatibility with release stat tracking code
|
||||
return "GameStatsUpload_Ep2";
|
||||
}
|
||||
|
||||
|
||||
static char const *ep2Maps[] =
|
||||
{
|
||||
"ep2_outland_01",
|
||||
"ep2_outland_02",
|
||||
"ep2_outland_03",
|
||||
"ep2_outland_04",
|
||||
"ep2_outland_05",
|
||||
"ep2_outland_06",
|
||||
"ep2_outland_06a",
|
||||
"ep2_outland_07",
|
||||
"ep2_outland_08",
|
||||
"ep2_outland_09",
|
||||
"ep2_outland_10",
|
||||
"ep2_outland_10a",
|
||||
"ep2_outland_11",
|
||||
"ep2_outland_11a",
|
||||
"ep2_outland_12",
|
||||
"ep2_outland_12a"
|
||||
};
|
||||
|
||||
|
||||
bool CEP2GameStats::UserPlayedAllTheMaps( void )
|
||||
{
|
||||
int c = ARRAYSIZE( ep2Maps );
|
||||
for ( int i = 0; i < c; ++i )
|
||||
{
|
||||
int idx = m_BasicStats.m_MapTotals.Find( ep2Maps[ i ] );
|
||||
if( idx == m_BasicStats.m_MapTotals.InvalidIndex() )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Destructor
|
||||
// Input : -
|
||||
//-----------------------------------------------------------------------------
|
||||
CEP2GameStats::~CEP2GameStats()
|
||||
{
|
||||
m_pCurrentMap = NULL;
|
||||
m_dictMapStats.Purge();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : &SaveBuffer -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CEP2GameStats::AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer )
|
||||
{
|
||||
// Save data per map.
|
||||
for ( int iMap = m_dictMapStats.First(); iMap != m_dictMapStats.InvalidIndex(); iMap = m_dictMapStats.Next( iMap ) )
|
||||
{
|
||||
// Get the current map.
|
||||
Ep2LevelStats_t *pCurrentMap = &m_dictMapStats[iMap];
|
||||
Assert( pCurrentMap );
|
||||
pCurrentMap->AppendToBuffer( SaveBuffer );
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer )
|
||||
{
|
||||
Ep2LevelStats_t::LoadData( m_dictMapStats, LoadBuffer );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CEP2GameStats::Event_LevelInit( void )
|
||||
{
|
||||
BaseClass::Event_LevelInit();
|
||||
|
||||
char const *pchTag = NULL;
|
||||
CommandLine()->CheckParm( "-gamestatstag", &pchTag );
|
||||
if ( !pchTag )
|
||||
{
|
||||
pchTag = "";
|
||||
}
|
||||
|
||||
m_pCurrentMap = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
|
||||
m_pCurrentMap->Init( STRING( gpGlobals->mapname ), gpGlobals->curtime, pchTag, gpGlobals->mapversion );
|
||||
}
|
||||
|
||||
Ep2LevelStats_t::EntityDeathsLump_t *CEP2GameStats::FindDeathsLump( char const *npcName )
|
||||
{
|
||||
if ( !m_pCurrentMap )
|
||||
return NULL;
|
||||
|
||||
char const *name = npcName;
|
||||
// Hack to fixup name
|
||||
if ( !Q_stricmp( name, "npc_ministrider" ) )
|
||||
{
|
||||
name = "npc_hunter";
|
||||
}
|
||||
|
||||
if ( Q_strnicmp( name, "npc_", 4 ) )
|
||||
return NULL;
|
||||
|
||||
int idx = m_pCurrentMap->m_dictEntityDeaths.Find( name );
|
||||
if ( idx == m_pCurrentMap->m_dictEntityDeaths.InvalidIndex() )
|
||||
{
|
||||
idx = m_pCurrentMap->m_dictEntityDeaths.Insert( name );
|
||||
}
|
||||
|
||||
return &m_pCurrentMap->m_dictEntityDeaths[ idx ];
|
||||
}
|
||||
|
||||
Ep2LevelStats_t::WeaponLump_t *CEP2GameStats::FindWeaponsLump( char const *pchWeaponName, bool bPrimary )
|
||||
{
|
||||
if ( !m_pCurrentMap )
|
||||
return NULL;
|
||||
|
||||
if ( !pchWeaponName )
|
||||
{
|
||||
AssertOnce( !"FindWeaponsLump pchWeaponName == NULL" );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char lookup[ 512 ];
|
||||
Q_snprintf( lookup, sizeof( lookup ), "%s_%s", pchWeaponName, bPrimary ? "primary" : "secondary" );
|
||||
int idx = m_pCurrentMap->m_dictWeapons.Find( lookup );
|
||||
if ( idx == m_pCurrentMap->m_dictWeapons.InvalidIndex() )
|
||||
{
|
||||
idx = m_pCurrentMap->m_dictWeapons.Insert( lookup );
|
||||
}
|
||||
|
||||
return &m_pCurrentMap->m_dictWeapons[ idx ];
|
||||
}
|
||||
|
||||
// Finds the generic stats lump
|
||||
Ep2LevelStats_t::GenericStatsLump_t *CEP2GameStats::FindGenericLump( char const *pchStatName )
|
||||
{
|
||||
if ( !m_pCurrentMap )
|
||||
return NULL;
|
||||
if ( !pchStatName || !*pchStatName )
|
||||
return NULL;
|
||||
|
||||
int idx = m_pCurrentMap->m_dictGeneric.Find( pchStatName );
|
||||
if ( idx == m_pCurrentMap->m_dictGeneric.InvalidIndex() )
|
||||
{
|
||||
idx = m_pCurrentMap->m_dictGeneric.Insert( pchStatName );
|
||||
}
|
||||
|
||||
return &m_pCurrentMap->m_dictGeneric[ idx ];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *szMapName -
|
||||
// Output : Ep2LevelStats_t
|
||||
//-----------------------------------------------------------------------------
|
||||
Ep2LevelStats_t *CEP2GameStats::FindOrAddMapStats( const char *szMapName )
|
||||
{
|
||||
int iMap = m_dictMapStats.Find( szMapName );
|
||||
if( iMap == m_dictMapStats.InvalidIndex() )
|
||||
{
|
||||
iMap = m_dictMapStats.Insert( szMapName );
|
||||
}
|
||||
|
||||
return &m_dictMapStats[iMap];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CEP2GameStats::Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info )
|
||||
{
|
||||
BaseClass::Event_PlayerDamage( pBasePlayer, info );
|
||||
|
||||
m_pCurrentMap->m_FloatCounters[ Ep2LevelStats_t::COUNTER_DAMAGETAKEN ] += info.GetDamage();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CEP2GameStats::Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info )
|
||||
{
|
||||
BaseClass::Event_PlayerKilledOther( pAttacker, pVictim, info );
|
||||
|
||||
if ( pAttacker )
|
||||
{
|
||||
StatsLog( "Attacker: %s\n", pAttacker->GetClassname() );
|
||||
}
|
||||
|
||||
if ( !pVictim )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char const *pchVictim = pVictim->GetClassname();
|
||||
Ep2LevelStats_t::EntityDeathsLump_t *lump = FindDeathsLump( pchVictim );
|
||||
if ( lump )
|
||||
{
|
||||
++lump->m_nBodyCount;
|
||||
StatsLog( "Player has killed %d %s's\n", lump->m_nBodyCount, pchVictim );
|
||||
|
||||
CPropVehicleDriveable *veh = dynamic_cast< CPropVehicleDriveable * >( pAttacker );
|
||||
if ( !veh )
|
||||
veh = dynamic_cast< CPropVehicleDriveable * >( info.GetInflictor() );
|
||||
if ( veh )
|
||||
{
|
||||
CBaseEntity *driver = veh->GetDriver();
|
||||
if ( driver && driver->IsPlayer() )
|
||||
{
|
||||
++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ];
|
||||
StatsLog( " Vehicular homicide [%I64d] of %s's\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ], pchVictim );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StatsLog( "Player killed %s (not tracked)\n", pchVictim );
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_Punted( CBaseEntity *pObject )
|
||||
{
|
||||
BaseClass::Event_Punted( pObject );
|
||||
++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_OBJECTSPUNTED ];
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CEP2GameStats::Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info )
|
||||
{
|
||||
BaseClass::Event_PlayerKilled( pPlayer, info );
|
||||
|
||||
if ( info.GetDamageType() & DMG_FALL )
|
||||
{
|
||||
++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_FALLINGDEATHS ];
|
||||
}
|
||||
|
||||
Ep2LevelStats_t::PlayerDeathsLump_t death;
|
||||
|
||||
// set the location where the target died
|
||||
const Vector &org = pPlayer->GetAbsOrigin();
|
||||
death.nPosition[ 0 ] = static_cast<short>( org.x );
|
||||
death.nPosition[ 1 ] = static_cast<short>( org.y );
|
||||
death.nPosition[ 2 ] = static_cast<short>( org.z );
|
||||
|
||||
StatsLog( "CEP2GameStats::Event_PlayerKilled at location [%d %d %d]\n", (int)death.nPosition[ 0 ], (int)death.nPosition[ 1 ], (int)death.nPosition[ 2 ] );
|
||||
|
||||
// set the class of the attacker
|
||||
CBaseEntity *pInflictor = info.GetInflictor();
|
||||
CBaseEntity *pKiller = info.GetAttacker();
|
||||
|
||||
if ( pInflictor )
|
||||
{
|
||||
StatsLog( "Inflictor: %s\n", pInflictor->GetClassname() );
|
||||
}
|
||||
|
||||
if ( pKiller )
|
||||
{
|
||||
char const *pchKiller = pKiller->GetClassname();
|
||||
Ep2LevelStats_t::EntityDeathsLump_t *lump = FindDeathsLump( pchKiller );
|
||||
if ( lump )
|
||||
{
|
||||
++lump->m_nKilledPlayer;
|
||||
StatsLog( "Player has been killed %d times by %s's\n", lump->m_nKilledPlayer, pchKiller );
|
||||
}
|
||||
else
|
||||
{
|
||||
StatsLog( "Player killed by %s (not tracked)\n", pchKiller );
|
||||
}
|
||||
}
|
||||
|
||||
// add it to the list of deaths
|
||||
Ep2LevelStats_t *map = FindOrAddMapStats( STRING( gpGlobals->mapname ) );
|
||||
int slot = map->m_aPlayerDeaths.AddToTail( death );
|
||||
|
||||
Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
|
||||
if ( rec )
|
||||
{
|
||||
if ( rec->m_nFirstDeathIndex == -1 )
|
||||
{
|
||||
rec->m_nFirstDeathIndex = slot;
|
||||
}
|
||||
++rec->m_nNumDeaths;
|
||||
|
||||
StatsLog( "Player has died %d times since last save/load\n", rec->m_nNumDeaths );
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_CrateSmashed()
|
||||
{
|
||||
BaseClass::Event_CrateSmashed();
|
||||
|
||||
++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_CRATESSMASHED ];
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting )
|
||||
{
|
||||
BaseClass::Event_PlayerTraveled( pBasePlayer, distanceInInches, bInVehicle, bSprinting );
|
||||
|
||||
int iIndex = INVEHICLE;
|
||||
if ( !bInVehicle )
|
||||
{
|
||||
iIndex = bSprinting ? ONFOOTSPRINTING : ONFOOT;
|
||||
}
|
||||
|
||||
m_flInchesRemainder[ iIndex ] += distanceInInches;
|
||||
uint64 intPart = (uint64)m_flInchesRemainder[ iIndex ];
|
||||
m_flInchesRemainder[ iIndex ] -= intPart;
|
||||
if ( intPart > 0 )
|
||||
{
|
||||
if ( bInVehicle )
|
||||
{
|
||||
m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ] += intPart;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( bSprinting )
|
||||
{
|
||||
m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING ] += intPart;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ] += intPart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ep2LevelStats_t *map = m_pCurrentMap;
|
||||
if ( !map )
|
||||
return;
|
||||
|
||||
Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
|
||||
|
||||
if ( rec &&
|
||||
rec->m_nSaveHealth == -1 )
|
||||
{
|
||||
Vector pos = pBasePlayer->GetAbsOrigin();
|
||||
rec->m_nSavePos[ 0 ] = (short)pos.x;
|
||||
rec->m_nSavePos[ 1 ] = (short)pos.y;
|
||||
rec->m_nSavePos[ 2 ] = (short)pos.z;
|
||||
rec->m_nSaveHealth = clamp( pBasePlayer->GetHealth(), 0, 100 );
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName )
|
||||
{
|
||||
BaseClass::Event_WeaponFired( pShooter, bPrimary, pchWeaponName );
|
||||
|
||||
Ep2LevelStats_t::WeaponLump_t *lump = FindWeaponsLump( pchWeaponName, bPrimary );
|
||||
if ( lump )
|
||||
{
|
||||
++lump->m_nShots;
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info )
|
||||
{
|
||||
BaseClass::Event_WeaponHit( pShooter, bPrimary, pchWeaponName, info );
|
||||
Ep2LevelStats_t::WeaponLump_t *lump = FindWeaponsLump( pchWeaponName, bPrimary );
|
||||
if ( lump )
|
||||
{
|
||||
++lump->m_nHits;
|
||||
lump->m_flDamageInflicted += info.GetDamage();
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_SaveGame( void )
|
||||
{
|
||||
BaseClass::Event_SaveGame();
|
||||
|
||||
Ep2LevelStats_t *map = m_pCurrentMap;
|
||||
if ( !map )
|
||||
return;
|
||||
|
||||
++map->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ];
|
||||
StatsLog( " %I64uth save on this map\n", map->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ] );
|
||||
|
||||
char const *pchSaveFile = engine->GetSaveFileName();
|
||||
if ( !pchSaveFile || !pchSaveFile[ 0 ] )
|
||||
return;
|
||||
|
||||
char name[ 512 ];
|
||||
Q_strncpy( name, pchSaveFile, sizeof( name ) );
|
||||
Q_strlower( name );
|
||||
Q_FixSlashes( name );
|
||||
|
||||
unsigned int uFileTime = filesystem->GetFileTime( name, "GAME" );
|
||||
// Latch off previous
|
||||
map->m_SaveGameInfo.Latch( name, uFileTime );
|
||||
|
||||
Ep2LevelStats_t::SaveGameInfoRecord2_t *rec = map->m_SaveGameInfo.m_pCurrentRecord;
|
||||
|
||||
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
||||
if ( pPlayer )
|
||||
{
|
||||
Vector pos = pPlayer->GetAbsOrigin();
|
||||
rec->m_nSavePos[ 0 ] = (short)pos.x;
|
||||
rec->m_nSavePos[ 1 ] = (short)pos.y;
|
||||
rec->m_nSavePos[ 2 ] = (short)pos.z;
|
||||
rec->m_nSaveHealth = clamp( pPlayer->GetHealth(), 0, 100 );
|
||||
rec->m_SaveType = Q_stristr( pchSaveFile, "autosave" ) ?
|
||||
Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_AUTOSAVE : Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_USERSAVE;
|
||||
|
||||
StatsLog( "save pos %i %i %i w/ health %d\n",
|
||||
rec->m_nSavePos[ 0 ],
|
||||
rec->m_nSavePos[ 1 ],
|
||||
rec->m_nSavePos[ 2 ],
|
||||
rec->m_nSaveHealth );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_LoadGame( void )
|
||||
{
|
||||
BaseClass::Event_LoadGame();
|
||||
|
||||
Ep2LevelStats_t *map = m_pCurrentMap;
|
||||
if ( !map )
|
||||
return;
|
||||
|
||||
++map->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ];
|
||||
StatsLog( " %I64uth load on this map\n", map->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ] );
|
||||
|
||||
char const *pchSaveFile = engine->GetMostRecentlyLoadedFileName();
|
||||
if ( !pchSaveFile || !pchSaveFile[ 0 ] )
|
||||
return;
|
||||
|
||||
char name[ 512 ];
|
||||
Q_snprintf( name, sizeof( name ), "save/%s", pchSaveFile );
|
||||
Q_DefaultExtension( name, IsX360() ? ".360.sav" : ".sav", sizeof( name ) );
|
||||
Q_FixSlashes( name );
|
||||
Q_strlower( name );
|
||||
|
||||
Ep2LevelStats_t::SaveGameInfo_t *pSaveGameInfo = &map->m_SaveGameInfo;
|
||||
|
||||
if ( pSaveGameInfo->m_nCurrentSaveFileTime == 0 ||
|
||||
pSaveGameInfo->m_sCurrentSaveFile != name )
|
||||
{
|
||||
unsigned int uFileTime = filesystem->GetFileTime( name, "GAME" );
|
||||
|
||||
// Latch off previous
|
||||
StatsLog( "Relatching save game file due to time or filename change (%s : %u)\n", name, uFileTime );
|
||||
pSaveGameInfo->Latch( name, uFileTime );
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle )
|
||||
{
|
||||
BaseClass::Event_FlippedVehicle( pDriver, pVehicle );
|
||||
++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ];
|
||||
StatsLog( "%I64u time vehicle overturned\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ] );
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame )
|
||||
{
|
||||
BaseClass::Event_PreSaveGameLoaded( pSaveName, bInGame );
|
||||
|
||||
// Not currently in a level
|
||||
if ( !bInGame )
|
||||
return;
|
||||
|
||||
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
||||
if ( !pPlayer )
|
||||
return;
|
||||
|
||||
// We're loading a saved game while the player is still alive (are they stuck?)
|
||||
if ( pPlayer->IsAlive() )
|
||||
{
|
||||
++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ];
|
||||
StatsLog( "%I64u game loaded with living player\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ] );
|
||||
}
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer )
|
||||
{
|
||||
BaseClass::Event_PlayerEnteredGodMode( pBasePlayer );
|
||||
++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ];
|
||||
StatsLog( "%I64u time entering godmode\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ] );
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer )
|
||||
{
|
||||
BaseClass::Event_PlayerEnteredNoClip( pBasePlayer );
|
||||
++m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ];
|
||||
StatsLog( "%I64u time entering NOCLIP\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] );
|
||||
}
|
||||
|
||||
void CEP2GameStats::Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer )
|
||||
{
|
||||
BaseClass::Event_DecrementPlayerEnteredNoClip( pBasePlayer );
|
||||
if ( m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] > 0 )
|
||||
{
|
||||
--m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ];
|
||||
}
|
||||
StatsLog( "%I64u decrement entering NOCLIP (entering vehicle doesn't count)\n", m_pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] );
|
||||
}
|
||||
|
||||
// Generic statistics lump
|
||||
void CEP2GameStats::Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount )
|
||||
{
|
||||
BaseClass::Event_IncrementCountedStatistic( vecAbsOrigin, pchStatisticName, flIncrementAmount );
|
||||
|
||||
// Find the generic lump
|
||||
Ep2LevelStats_t::GenericStatsLump_t *lump = FindGenericLump( pchStatisticName );
|
||||
if ( lump )
|
||||
{
|
||||
lump->m_Pos[ 0 ] = (short)vecAbsOrigin.x;
|
||||
lump->m_Pos[ 1 ] = (short)vecAbsOrigin.y;
|
||||
lump->m_Pos[ 2 ] = (short)vecAbsOrigin.z;
|
||||
lump->m_flCurrentValue += (double)flIncrementAmount;
|
||||
++lump->m_unCount;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
static void CC_ListDeaths( const CCommand &args )
|
||||
{
|
||||
Ep2LevelStats_t *map = s_CEP2GameStats_Singleton.FindOrAddMapStats( STRING( gpGlobals->mapname ) );
|
||||
if ( !map )
|
||||
return;
|
||||
|
||||
int nRendered = 0;
|
||||
for ( int i = map->m_aPlayerDeaths.Count() - 1; i >= 0 ; --i, ++nRendered )
|
||||
{
|
||||
Vector org( map->m_aPlayerDeaths[ i ].nPosition[ 0 ],
|
||||
map->m_aPlayerDeaths[ i ].nPosition[ 1 ],
|
||||
map->m_aPlayerDeaths[ i ].nPosition[ 2 ] + 36.0f );
|
||||
|
||||
// FIXME: This might overflow
|
||||
NDebugOverlay::Box( org, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ), 0, 255, 0, 128, 10.0f );
|
||||
|
||||
/*
|
||||
Msg( "%s killed %s with %s at (%d,%d,%d)\n",
|
||||
g_aClassNames[ map->m_aPlayerDeaths[ i ].iAttackClass ],
|
||||
g_aClassNames[ map->m_aPlayerDeaths[ i ].iTargetClass ],
|
||||
WeaponIdToAlias( map->m_aPlayerDeaths[ i ].iWeapon ),
|
||||
map->m_aPlayerDeaths[ i ].nPosition[ 0 ],
|
||||
map->m_aPlayerDeaths[ i ].nPosition[ 1 ],
|
||||
map->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
|
||||
*/
|
||||
|
||||
if ( nRendered > 150 )
|
||||
break;
|
||||
}
|
||||
Msg( "\nlisted %d deaths\n", map->m_aPlayerDeaths.Count() );
|
||||
}
|
||||
|
||||
static ConCommand listDeaths("listdeaths", CC_ListDeaths, "lists player deaths", 0 );
|
||||
@@ -0,0 +1,532 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef EP2_GAMESTATS_H
|
||||
#define EP2_GAMESTATS_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "ep1_gamestats.h"
|
||||
#include "tier1/utlstring.h"
|
||||
|
||||
// EP2 Game Stats
|
||||
enum Ep2GameStatsVersions_t
|
||||
{
|
||||
EP2_GAMESTATS_FILE_VERSION_01 = 001,
|
||||
EP2_GAMESTATS_FILE_VERSION_02 = 002,
|
||||
|
||||
EP2_GAMESTATS_CURRENT_VERSION = EP2_GAMESTATS_FILE_VERSION_02,
|
||||
};
|
||||
|
||||
enum Ep2GameStatsLumpIds_t
|
||||
{
|
||||
EP2STATS_LUMP_HEADER = 1,
|
||||
EP2STATS_LUMP_DEATH,
|
||||
EP2STATS_LUMP_NPC,
|
||||
EP2STATS_LUMP_WEAPON,
|
||||
EP2STATS_LUMP_SAVEGAMEINFO,
|
||||
EP2STATS_LUMP_TAG,
|
||||
EP2STATS_LUMP_GENERIC,
|
||||
EP2_MAX_LUMP_COUNT
|
||||
};
|
||||
|
||||
// EP2 Game Level Stats Data
|
||||
struct Ep2LevelStats_t
|
||||
{
|
||||
public:
|
||||
enum FloatCounterTypes_t
|
||||
{
|
||||
COUNTER_DAMAGETAKEN = 0,
|
||||
|
||||
NUM_FLOATCOUNTER_TYPES,
|
||||
};
|
||||
|
||||
enum IntCounterTypes_t
|
||||
{
|
||||
COUNTER_CRATESSMASHED = 0,
|
||||
COUNTER_OBJECTSPUNTED,
|
||||
COUNTER_VEHICULARHOMICIDES,
|
||||
COUNTER_DISTANCE_INVEHICLE,
|
||||
COUNTER_DISTANCE_ONFOOT,
|
||||
COUNTER_DISTANCE_ONFOOTSPRINTING,
|
||||
COUNTER_FALLINGDEATHS,
|
||||
COUNTER_VEHICLE_OVERTURNED,
|
||||
COUNTER_LOADGAME_STILLALIVE,
|
||||
COUNTER_LOADS,
|
||||
COUNTER_SAVES,
|
||||
COUNTER_GODMODES,
|
||||
COUNTER_NOCLIPS,
|
||||
|
||||
NUM_INTCOUNTER_TYPES,
|
||||
};
|
||||
|
||||
Ep2LevelStats_t() :
|
||||
m_bInitialized( false ),
|
||||
m_flLevelStartTime( 0.0f )
|
||||
{
|
||||
Q_memset( m_IntCounters, 0, sizeof( m_IntCounters ) );
|
||||
Q_memset( m_FloatCounters, 0, sizeof( m_FloatCounters ) );
|
||||
}
|
||||
~Ep2LevelStats_t()
|
||||
{
|
||||
}
|
||||
|
||||
Ep2LevelStats_t( const Ep2LevelStats_t &other )
|
||||
{
|
||||
m_bInitialized = other.m_bInitialized;
|
||||
m_flLevelStartTime = other.m_flLevelStartTime;
|
||||
m_Header = other.m_Header;
|
||||
m_aPlayerDeaths = other.m_aPlayerDeaths;
|
||||
Q_memcpy( m_IntCounters, other.m_IntCounters, sizeof( m_IntCounters ) );
|
||||
Q_memcpy( m_FloatCounters, other.m_FloatCounters, sizeof( m_FloatCounters ) );
|
||||
int i;
|
||||
for ( i = other.m_dictEntityDeaths.First(); i != other.m_dictEntityDeaths.InvalidIndex(); i = other.m_dictEntityDeaths.Next( i ) )
|
||||
{
|
||||
m_dictEntityDeaths.Insert( other.m_dictEntityDeaths.GetElementName( i ), other.m_dictEntityDeaths[ i ] );
|
||||
}
|
||||
for ( i = other.m_dictWeapons.First(); i != other.m_dictWeapons.InvalidIndex(); i = other.m_dictWeapons.Next( i ) )
|
||||
{
|
||||
m_dictWeapons.Insert( other.m_dictWeapons.GetElementName( i ), other.m_dictWeapons[ i ] );
|
||||
}
|
||||
m_SaveGameInfo = other.m_SaveGameInfo;
|
||||
}
|
||||
|
||||
// Create and destroy.
|
||||
void Init( const char *pszMapName, float flStartTime, char const *pchTag, int nMapVersion )
|
||||
{
|
||||
// Initialize.
|
||||
m_Header.m_iVersion = EP2_GAMESTATS_CURRENT_VERSION;
|
||||
Q_strncpy( m_Header.m_szMapName, pszMapName, sizeof( m_Header.m_szMapName ) );
|
||||
m_Header.m_flTime = 0.0f;
|
||||
|
||||
// Start the level timer.
|
||||
m_flLevelStartTime = flStartTime;
|
||||
|
||||
Q_strncpy( m_Tag.m_szTagText, pchTag, sizeof( m_Tag.m_szTagText ) );
|
||||
m_Tag.m_nMapVersion = nMapVersion;
|
||||
}
|
||||
|
||||
void Shutdown( float flEndTime )
|
||||
{
|
||||
m_Header.m_flTime = flEndTime - m_flLevelStartTime;
|
||||
}
|
||||
|
||||
void AppendToBuffer( CUtlBuffer &SaveBuffer )
|
||||
{
|
||||
// Always write out as current version
|
||||
m_Header.m_iVersion = EP2_GAMESTATS_CURRENT_VERSION;
|
||||
|
||||
// Write out the lumps.
|
||||
CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_HEADER, 1, sizeof( Ep2LevelStats_t::LevelHeader_t ), static_cast<void*>( &m_Header ) );
|
||||
CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_TAG, 1, sizeof( Ep2LevelStats_t::Tag_t ), static_cast< void * >( &m_Tag ) );
|
||||
CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_DEATH, m_aPlayerDeaths.Count(), sizeof( Ep2LevelStats_t::PlayerDeathsLump_t ), static_cast<void*>( m_aPlayerDeaths.Base() ) );
|
||||
{
|
||||
CUtlBuffer buf;
|
||||
buf.Put( (const void *)m_IntCounters, sizeof( m_IntCounters ) );
|
||||
buf.Put( (const void *)m_FloatCounters, sizeof( m_FloatCounters ) );
|
||||
buf.PutInt( m_dictEntityDeaths.Count() );
|
||||
for ( int i = m_dictEntityDeaths.First(); i != m_dictEntityDeaths.InvalidIndex(); i = m_dictEntityDeaths.Next( i ) )
|
||||
{
|
||||
buf.PutString( m_dictEntityDeaths.GetElementName( i ) );
|
||||
buf.Put( (const void *)&m_dictEntityDeaths[ i ], sizeof( Ep2LevelStats_t::EntityDeathsLump_t ) );
|
||||
}
|
||||
CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_NPC, 1, buf.TellPut(), buf.Base() );
|
||||
}
|
||||
{
|
||||
CUtlBuffer buf;
|
||||
buf.PutInt( m_dictWeapons.Count() );
|
||||
for ( int i = m_dictWeapons.First(); i != m_dictWeapons.InvalidIndex(); i = m_dictWeapons.Next( i ) )
|
||||
{
|
||||
buf.PutString( m_dictWeapons.GetElementName( i ) );
|
||||
buf.Put( (const void *)&m_dictWeapons[ i ], sizeof( Ep2LevelStats_t::WeaponLump_t ) );
|
||||
}
|
||||
CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_WEAPON, 1, buf.TellPut(), buf.Base() );
|
||||
}
|
||||
{
|
||||
CUtlBuffer buf;
|
||||
buf.PutString( m_SaveGameInfo.m_sCurrentSaveFile.String() );
|
||||
buf.PutInt( m_SaveGameInfo.m_nCurrentSaveFileTime );
|
||||
buf.PutInt( m_SaveGameInfo.m_Records.Count() );
|
||||
for ( int i = 0 ; i < m_SaveGameInfo.m_Records.Count(); ++i )
|
||||
{
|
||||
buf.Put( (const void *)&m_SaveGameInfo.m_Records[ i ], sizeof( Ep2LevelStats_t::SaveGameInfoRecord2_t ) );
|
||||
}
|
||||
CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_SAVEGAMEINFO, 1, buf.TellPut(), buf.Base() );
|
||||
}
|
||||
{
|
||||
CUtlBuffer buf;
|
||||
buf.PutShort( Ep2LevelStats_t::GenericStatsLump_t::LumpVersion );
|
||||
buf.PutInt( m_dictGeneric.Count() );
|
||||
for ( int i = m_dictGeneric.First(); i != m_dictGeneric.InvalidIndex(); i = m_dictGeneric.Next( i ) )
|
||||
{
|
||||
buf.PutString( m_dictGeneric.GetElementName( i ) );
|
||||
buf.Put( (const void *)&m_dictGeneric[ i ], sizeof( Ep2LevelStats_t::GenericStatsLump_t ) );
|
||||
}
|
||||
CBaseGameStats::AppendLump( EP2_MAX_LUMP_COUNT, SaveBuffer, EP2STATS_LUMP_GENERIC, 1, buf.TellPut(), buf.Base() );
|
||||
}
|
||||
}
|
||||
|
||||
static void LoadData( CUtlDict<Ep2LevelStats_t, unsigned short>& items, CUtlBuffer &LoadBuffer )
|
||||
{
|
||||
// Read the next lump.
|
||||
unsigned short iLump = 0;
|
||||
unsigned short iLumpCount = 0;
|
||||
|
||||
Ep2LevelStats_t *pItem = NULL;
|
||||
|
||||
while( CBaseGameStats::GetLumpHeader( EP2_MAX_LUMP_COUNT, LoadBuffer, iLump, iLumpCount, true ) )
|
||||
{
|
||||
switch ( iLump )
|
||||
{
|
||||
case EP2STATS_LUMP_HEADER:
|
||||
{
|
||||
Ep2LevelStats_t::LevelHeader_t header;
|
||||
CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::LevelHeader_t ), &header );
|
||||
pItem = &items[ items.Insert( header.m_szMapName ) ];
|
||||
pItem->m_Header = header;
|
||||
pItem->m_Tag.Clear();
|
||||
Assert( pItem );
|
||||
}
|
||||
break;
|
||||
case EP2STATS_LUMP_TAG:
|
||||
{
|
||||
Assert( pItem );
|
||||
CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::Tag_t ), &pItem->m_Tag );
|
||||
}
|
||||
break;
|
||||
case EP2STATS_LUMP_DEATH:
|
||||
{
|
||||
Assert( pItem );
|
||||
pItem->m_aPlayerDeaths.SetCount( iLumpCount );
|
||||
CBaseGameStats::LoadLump( LoadBuffer, iLumpCount, sizeof( Ep2LevelStats_t::PlayerDeathsLump_t ), static_cast<void*>( pItem->m_aPlayerDeaths.Base() ) );
|
||||
}
|
||||
break;
|
||||
case EP2STATS_LUMP_NPC:
|
||||
{
|
||||
Assert( pItem );
|
||||
LoadBuffer.Get( ( void * )pItem->m_IntCounters, sizeof( pItem->m_IntCounters ) );
|
||||
LoadBuffer.Get( ( void * )pItem->m_FloatCounters, sizeof( pItem->m_FloatCounters ) );
|
||||
int c = LoadBuffer.GetInt();
|
||||
for ( int i = 0 ; i < c; ++i )
|
||||
{
|
||||
Ep2LevelStats_t::EntityDeathsLump_t data;
|
||||
char npcName[ 512 ];
|
||||
LoadBuffer.GetString( npcName );
|
||||
LoadBuffer.Get( &data, sizeof( data ) );
|
||||
pItem->m_dictEntityDeaths.Insert( npcName, data );
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EP2STATS_LUMP_WEAPON:
|
||||
{
|
||||
Assert( pItem );
|
||||
int c = LoadBuffer.GetInt();
|
||||
for ( int i = 0 ; i < c; ++i )
|
||||
{
|
||||
Ep2LevelStats_t::WeaponLump_t data;
|
||||
char weaponName[ 512 ];
|
||||
LoadBuffer.GetString( weaponName );
|
||||
LoadBuffer.Get( &data, sizeof( data ) );
|
||||
pItem->m_dictWeapons.Insert( weaponName, data );
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EP2STATS_LUMP_SAVEGAMEINFO:
|
||||
{
|
||||
Assert( pItem );
|
||||
Ep2LevelStats_t::SaveGameInfo_t *info = &pItem->m_SaveGameInfo;
|
||||
char sz[ 512 ];
|
||||
LoadBuffer.GetString( sz );
|
||||
info->m_sCurrentSaveFile = sz;
|
||||
info->m_nCurrentSaveFileTime = LoadBuffer.GetInt();
|
||||
int c = LoadBuffer.GetInt();
|
||||
for ( int i = 0 ; i < c; ++i )
|
||||
{
|
||||
Ep2LevelStats_t::SaveGameInfoRecord2_t rec;
|
||||
if ( pItem->m_Header.m_iVersion >= EP2_GAMESTATS_FILE_VERSION_02 )
|
||||
{
|
||||
LoadBuffer.Get( &rec, sizeof( rec ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t s = sizeof( Ep2LevelStats_t::SaveGameInfoRecord_t );
|
||||
LoadBuffer.Get( &rec, s );
|
||||
}
|
||||
info->m_Records.AddToTail( rec );
|
||||
}
|
||||
info->m_pCurrentRecord = NULL;
|
||||
if ( info->m_Records.Count() > 0 )
|
||||
{
|
||||
info->m_pCurrentRecord = &info->m_Records[ info->m_Records.Count() - 1 ];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EP2STATS_LUMP_GENERIC:
|
||||
{
|
||||
Assert( pItem );
|
||||
int version = LoadBuffer.GetShort();
|
||||
if ( version == Ep2LevelStats_t::GenericStatsLump_t::LumpVersion )
|
||||
{
|
||||
int c = LoadBuffer.GetInt();
|
||||
Assert( c < 2 * 1024 * 1024 );
|
||||
for ( int i = 0 ; i < c; ++i )
|
||||
{
|
||||
Ep2LevelStats_t::GenericStatsLump_t data;
|
||||
char pchStatName[ 512 ];
|
||||
LoadBuffer.GetString( pchStatName );
|
||||
LoadBuffer.Get( &data, sizeof( data ) );
|
||||
pItem->m_dictGeneric.Insert( pchStatName, data );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error( "Unsupported GenericStatsLump_t::LumpVersion" );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
// Level header data.
|
||||
struct LevelHeader_t
|
||||
{
|
||||
static const unsigned short LumpId = EP2STATS_LUMP_HEADER; // Lump ids.
|
||||
byte m_iVersion; // Version of the game stats file.
|
||||
char m_szMapName[64]; // Name of the map.
|
||||
float m_flTime; // Time spent in level.
|
||||
};
|
||||
|
||||
// Simple "tag" applied to all data in database (e.g., "PLAYTEST")
|
||||
struct Tag_t
|
||||
{
|
||||
static const unsigned short LumpId = EP2STATS_LUMP_TAG;
|
||||
|
||||
void Clear()
|
||||
{
|
||||
Q_memset( m_szTagText, 0, sizeof( m_szTagText ) );
|
||||
m_nMapVersion = 0;
|
||||
}
|
||||
|
||||
char m_szTagText[ 8 ];
|
||||
int m_nMapVersion;
|
||||
};
|
||||
|
||||
// Player deaths.
|
||||
struct PlayerDeathsLump_t
|
||||
{
|
||||
static const unsigned short LumpId = EP2STATS_LUMP_DEATH; // Lump ids.
|
||||
short nPosition[3]; // Position of death.
|
||||
// short iWeapon; // Weapon that killed the player.
|
||||
// byte iAttackClass; // Class that killed the player.
|
||||
// byte iTargetClass; // Class of the player killed.
|
||||
};
|
||||
|
||||
struct EntityDeathsLump_t
|
||||
{
|
||||
static const unsigned short LumpId = EP2STATS_LUMP_NPC;
|
||||
|
||||
EntityDeathsLump_t() :
|
||||
m_nBodyCount( 0u ),
|
||||
m_nKilledPlayer( 0u )
|
||||
{
|
||||
}
|
||||
|
||||
EntityDeathsLump_t( const EntityDeathsLump_t &other )
|
||||
{
|
||||
m_nBodyCount = other.m_nBodyCount;
|
||||
m_nKilledPlayer = other.m_nKilledPlayer;
|
||||
}
|
||||
|
||||
unsigned int m_nBodyCount; // Number killed by player
|
||||
unsigned int m_nKilledPlayer; // Number of times entity killed player
|
||||
};
|
||||
|
||||
struct WeaponLump_t
|
||||
{
|
||||
static const unsigned short LumpId = EP2STATS_LUMP_WEAPON;
|
||||
|
||||
WeaponLump_t() :
|
||||
m_nShots( 0 ),
|
||||
m_nHits( 0 ),
|
||||
m_flDamageInflicted( 0.0 )
|
||||
{
|
||||
}
|
||||
|
||||
WeaponLump_t( const WeaponLump_t &other )
|
||||
{
|
||||
m_nShots = other.m_nShots;
|
||||
m_nHits = other.m_nHits;
|
||||
m_flDamageInflicted = other.m_flDamageInflicted;
|
||||
}
|
||||
|
||||
unsigned int m_nShots;
|
||||
unsigned int m_nHits;
|
||||
double m_flDamageInflicted;
|
||||
};
|
||||
|
||||
struct SaveGameInfoRecord_t
|
||||
{
|
||||
SaveGameInfoRecord_t() :
|
||||
m_nFirstDeathIndex( -1 ),
|
||||
m_nNumDeaths( 0 ),
|
||||
m_nSaveHealth( -1 )
|
||||
{
|
||||
Q_memset( m_nSavePos, 0, sizeof( m_nSavePos ) );
|
||||
}
|
||||
|
||||
int m_nFirstDeathIndex;
|
||||
int m_nNumDeaths;
|
||||
// Health and player pos from the save file
|
||||
short m_nSavePos[ 3 ];
|
||||
short m_nSaveHealth;
|
||||
};
|
||||
|
||||
#pragma pack( 1 )
|
||||
// Adds save game type
|
||||
struct SaveGameInfoRecord2_t : public SaveGameInfoRecord_t
|
||||
{
|
||||
enum SaveType_t
|
||||
{
|
||||
TYPE_UNKNOWN = 0,
|
||||
TYPE_AUTOSAVE,
|
||||
TYPE_USERSAVE
|
||||
};
|
||||
|
||||
SaveGameInfoRecord2_t() :
|
||||
m_SaveType( (byte)TYPE_UNKNOWN )
|
||||
{
|
||||
}
|
||||
|
||||
byte m_SaveType;
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
struct SaveGameInfo_t
|
||||
{
|
||||
static const unsigned short LumpId = EP2STATS_LUMP_SAVEGAMEINFO;
|
||||
|
||||
SaveGameInfo_t() :
|
||||
m_nCurrentSaveFileTime( 0 ),
|
||||
m_pCurrentRecord( NULL )
|
||||
{
|
||||
}
|
||||
|
||||
void Latch( char const *pchSaveName, unsigned int uFileTime )
|
||||
{
|
||||
m_pCurrentRecord = &m_Records[ m_Records.AddToTail() ];
|
||||
m_nCurrentSaveFileTime = uFileTime;
|
||||
m_sCurrentSaveFile = pchSaveName;
|
||||
}
|
||||
|
||||
CUtlVector< SaveGameInfoRecord2_t > m_Records;
|
||||
SaveGameInfoRecord2_t *m_pCurrentRecord;
|
||||
unsigned int m_nCurrentSaveFileTime;
|
||||
CUtlString m_sCurrentSaveFile;
|
||||
};
|
||||
|
||||
struct GenericStatsLump_t
|
||||
{
|
||||
static const unsigned short LumpId = EP2STATS_LUMP_GENERIC;
|
||||
static const unsigned short LumpVersion = 1;
|
||||
|
||||
GenericStatsLump_t() :
|
||||
m_unCount( 0u ),
|
||||
m_flCurrentValue( 0.0 )
|
||||
{
|
||||
m_Pos[ 0 ] = m_Pos[ 1 ] = m_Pos[ 2 ] = 0;
|
||||
}
|
||||
|
||||
short m_Pos[ 3 ];
|
||||
unsigned int m_unCount;
|
||||
double m_flCurrentValue;
|
||||
};
|
||||
|
||||
// Data.
|
||||
LevelHeader_t m_Header; // Level header.
|
||||
Tag_t m_Tag;
|
||||
CUtlVector<PlayerDeathsLump_t> m_aPlayerDeaths; // List of player deaths.
|
||||
CUtlDict< EntityDeathsLump_t, int > m_dictEntityDeaths;
|
||||
CUtlDict< WeaponLump_t, int > m_dictWeapons;
|
||||
CUtlDict< GenericStatsLump_t, int > m_dictGeneric;
|
||||
|
||||
SaveGameInfo_t m_SaveGameInfo;
|
||||
float m_FloatCounters[ NUM_FLOATCOUNTER_TYPES ];
|
||||
uint64 m_IntCounters[ NUM_INTCOUNTER_TYPES ];
|
||||
|
||||
// Temporary data.
|
||||
bool m_bInitialized; // Has the map Map Stat Data been initialized.
|
||||
float m_flLevelStartTime;
|
||||
};
|
||||
|
||||
#if defined( GAME_DLL )
|
||||
class CEP2GameStats : public CEP1GameStats
|
||||
{
|
||||
typedef CEP1GameStats BaseClass;
|
||||
|
||||
public:
|
||||
CEP2GameStats();
|
||||
virtual ~CEP2GameStats();
|
||||
|
||||
virtual CBaseGameStats *OnInit( CBaseGameStats *pCurrentGameStats, char const *gamedir ) { return pCurrentGameStats; }
|
||||
|
||||
virtual bool UserPlayedAllTheMaps( void );
|
||||
virtual const char *GetStatSaveFileName( void );
|
||||
virtual const char *GetStatUploadRegistryKeyName( void );
|
||||
|
||||
// Buffers.
|
||||
virtual void AppendCustomDataToSaveBuffer( CUtlBuffer &SaveBuffer );
|
||||
virtual void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer );
|
||||
|
||||
// Events
|
||||
virtual void Event_LevelInit( void );
|
||||
virtual void Event_PlayerKilled( CBasePlayer *pPlayer, const CTakeDamageInfo &info );
|
||||
virtual void Event_PlayerDamage( CBasePlayer *pBasePlayer, const CTakeDamageInfo &info );
|
||||
virtual void Event_PlayerKilledOther( CBasePlayer *pAttacker, CBaseEntity *pVictim, const CTakeDamageInfo &info );
|
||||
virtual void Event_CrateSmashed();
|
||||
virtual void Event_Punted( CBaseEntity *pObject );
|
||||
virtual void Event_PlayerTraveled( CBasePlayer *pBasePlayer, float distanceInInches, bool bInVehicle, bool bSprinting );
|
||||
virtual void Event_WeaponFired( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName );
|
||||
virtual void Event_WeaponHit( CBasePlayer *pShooter, bool bPrimary, char const *pchWeaponName, const CTakeDamageInfo &info );
|
||||
virtual void Event_SaveGame( void );
|
||||
virtual void Event_LoadGame( void );
|
||||
virtual void Event_FlippedVehicle( CBasePlayer *pDriver, CPropVehicleDriveable *pVehicle );
|
||||
// Called before .sav file is actually loaded (player should still be in previous level, if any)
|
||||
virtual void Event_PreSaveGameLoaded( char const *pSaveName, bool bInGame );
|
||||
virtual void Event_PlayerEnteredGodMode( CBasePlayer *pBasePlayer );
|
||||
virtual void Event_PlayerEnteredNoClip( CBasePlayer *pBasePlayer );
|
||||
virtual void Event_DecrementPlayerEnteredNoClip( CBasePlayer *pBasePlayer );
|
||||
// Generic statistics lump
|
||||
virtual void Event_IncrementCountedStatistic( const Vector& vecAbsOrigin, char const *pchStatisticName, float flIncrementAmount );
|
||||
|
||||
public: //FIXME: temporary used for CC_ListDeaths command
|
||||
Ep2LevelStats_t *FindOrAddMapStats( const char *szMapName );
|
||||
|
||||
public:
|
||||
|
||||
Ep2LevelStats_t::EntityDeathsLump_t *FindDeathsLump( char const *npcName );
|
||||
Ep2LevelStats_t::WeaponLump_t *FindWeaponsLump( char const *pchWeaponName, bool bPrimary );
|
||||
Ep2LevelStats_t::GenericStatsLump_t *FindGenericLump( char const *pchStatName );
|
||||
// Utilities.
|
||||
Ep2LevelStats_t *GetCurrentMap( void ) { return m_pCurrentMap; }
|
||||
|
||||
Ep2LevelStats_t *m_pCurrentMap;
|
||||
CUtlDict<Ep2LevelStats_t, unsigned short> m_dictMapStats;
|
||||
enum
|
||||
{
|
||||
INVEHICLE = 0,
|
||||
ONFOOT,
|
||||
ONFOOTSPRINTING,
|
||||
|
||||
NUM_TRAVEL_TYPES
|
||||
};
|
||||
float m_flInchesRemainder[ NUM_TRAVEL_TYPES ];
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // EP2_GAMESTATS_H
|
||||
@@ -0,0 +1,581 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Gravity well device
|
||||
//
|
||||
//=====================================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "grenade_hopwire.h"
|
||||
#include "rope.h"
|
||||
#include "rope_shared.h"
|
||||
#include "beam_shared.h"
|
||||
#include "physics.h"
|
||||
#include "physics_saverestore.h"
|
||||
#include "explode.h"
|
||||
#include "physics_prop_ragdoll.h"
|
||||
#include "movevars_shared.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
ConVar hopwire_vortex( "hopwire_vortex", "0" );
|
||||
ConVar hopwire_trap( "hopwire_trap", "1" );
|
||||
ConVar hopwire_strider_kill_dist_h( "hopwire_strider_kill_dist_h", "300" );
|
||||
ConVar hopwire_strider_kill_dist_v( "hopwire_strider_kill_dist_v", "256" );
|
||||
ConVar hopwire_strider_hits( "hopwire_strider_hits", "1" );
|
||||
ConVar hopwire_hopheight( "hopwire_hopheight", "400" );
|
||||
|
||||
ConVar g_debug_hopwire( "g_debug_hopwire", "0" );
|
||||
|
||||
#define DENSE_BALL_MODEL "models/props_junk/metal_paintcan001b.mdl"
|
||||
|
||||
#define MAX_HOP_HEIGHT (hopwire_hopheight.GetFloat()) // Maximum amount the grenade will "hop" upwards when detonated
|
||||
|
||||
class CGravityVortexController : public CBaseEntity
|
||||
{
|
||||
DECLARE_CLASS( CGravityVortexController, CBaseEntity );
|
||||
DECLARE_DATADESC();
|
||||
|
||||
public:
|
||||
|
||||
CGravityVortexController( void ) : m_flEndTime( 0.0f ), m_flRadius( 256 ), m_flStrength( 256 ), m_flMass( 0.0f ) {}
|
||||
float GetConsumedMass( void ) const;
|
||||
|
||||
static CGravityVortexController *Create( const Vector &origin, float radius, float strength, float duration );
|
||||
|
||||
private:
|
||||
|
||||
void ConsumeEntity( CBaseEntity *pEnt );
|
||||
void PullPlayersInRange( void );
|
||||
bool KillNPCInRange( CBaseEntity *pVictim, IPhysicsObject **pPhysObj );
|
||||
void CreateDenseBall( void );
|
||||
void PullThink( void );
|
||||
void StartPull( const Vector &origin, float radius, float strength, float duration );
|
||||
|
||||
float m_flMass; // Mass consumed by the vortex
|
||||
float m_flEndTime; // Time when the vortex will stop functioning
|
||||
float m_flRadius; // Area of effect for the vortex
|
||||
float m_flStrength; // Pulling strength of the vortex
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Returns the amount of mass consumed by the vortex
|
||||
//-----------------------------------------------------------------------------
|
||||
float CGravityVortexController::GetConsumedMass( void ) const
|
||||
{
|
||||
return m_flMass;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Adds the entity's mass to the aggregate mass consumed
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGravityVortexController::ConsumeEntity( CBaseEntity *pEnt )
|
||||
{
|
||||
// Get our base physics object
|
||||
IPhysicsObject *pPhysObject = pEnt->VPhysicsGetObject();
|
||||
if ( pPhysObject == NULL )
|
||||
return;
|
||||
|
||||
// Ragdolls need to report the sum of all their parts
|
||||
CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pEnt );
|
||||
if ( pRagdoll != NULL )
|
||||
{
|
||||
// Find the aggregate mass of the whole ragdoll
|
||||
ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll();
|
||||
for ( int j = 0; j < pRagdollPhys->listCount; ++j )
|
||||
{
|
||||
m_flMass += pRagdollPhys->list[j].pObject->GetMass();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise we just take the normal mass
|
||||
m_flMass += pPhysObject->GetMass();
|
||||
}
|
||||
|
||||
// Destroy the entity
|
||||
UTIL_Remove( pEnt );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Causes players within the radius to be sucked in
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGravityVortexController::PullPlayersInRange( void )
|
||||
{
|
||||
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
|
||||
|
||||
Vector vecForce = GetAbsOrigin() - pPlayer->WorldSpaceCenter();
|
||||
float dist = VectorNormalize( vecForce );
|
||||
|
||||
// FIXME: Need a more deterministic method here
|
||||
if ( dist < 128.0f )
|
||||
{
|
||||
// Kill the player (with falling death sound and effects)
|
||||
CTakeDamageInfo deathInfo( this, this, GetAbsOrigin(), GetAbsOrigin(), 200, DMG_FALL );
|
||||
pPlayer->TakeDamage( deathInfo );
|
||||
|
||||
if ( pPlayer->IsAlive() == false )
|
||||
{
|
||||
color32 black = { 0, 0, 0, 255 };
|
||||
UTIL_ScreenFade( pPlayer, black, 0.1f, 0.0f, (FFADE_OUT|FFADE_STAYOUT) );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Must be within the radius
|
||||
if ( dist > m_flRadius )
|
||||
return;
|
||||
|
||||
float mass = pPlayer->VPhysicsGetObject()->GetMass();
|
||||
float playerForce = m_flStrength * 0.05f;
|
||||
|
||||
// Find the pull force
|
||||
// NOTE: We might want to make this non-linear to give more of a "grace distance"
|
||||
vecForce *= ( 1.0f - ( dist / m_flRadius ) ) * playerForce * mass;
|
||||
vecForce[2] *= 0.025f;
|
||||
|
||||
pPlayer->SetBaseVelocity( vecForce );
|
||||
pPlayer->AddFlag( FL_BASEVELOCITY );
|
||||
|
||||
// Make sure the player moves
|
||||
if ( vecForce.z > 0 && ( pPlayer->GetFlags() & FL_ONGROUND) )
|
||||
{
|
||||
pPlayer->SetGroundEntity( NULL );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Attempts to kill an NPC if it's within range and other criteria
|
||||
// Input : *pVictim - NPC to assess
|
||||
// **pPhysObj - pointer to the ragdoll created if the NPC is killed
|
||||
// Output : bool - whether or not the NPC was killed and the returned pointer is valid
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGravityVortexController::KillNPCInRange( CBaseEntity *pVictim, IPhysicsObject **pPhysObj )
|
||||
{
|
||||
CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer();
|
||||
|
||||
// See if we can ragdoll
|
||||
if ( pBCC != NULL && pBCC->CanBecomeRagdoll() )
|
||||
{
|
||||
// Don't bother with striders
|
||||
if ( FClassnameIs( pBCC, "npc_strider" ) )
|
||||
return false;
|
||||
|
||||
// TODO: Make this an interaction between the NPC and the vortex
|
||||
|
||||
// Become ragdoll
|
||||
CTakeDamageInfo info( this, this, 1.0f, DMG_GENERIC );
|
||||
CBaseEntity *pRagdoll = CreateServerRagdoll( pBCC, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
|
||||
pRagdoll->SetCollisionBounds( pVictim->CollisionProp()->OBBMins(), pVictim->CollisionProp()->OBBMaxs() );
|
||||
|
||||
// Necessary to cause it to do the appropriate death cleanup
|
||||
CTakeDamageInfo ragdollInfo( this, this, 10000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL );
|
||||
pVictim->TakeDamage( ragdollInfo );
|
||||
|
||||
// Return the pointer to the ragdoll
|
||||
*pPhysObj = pRagdoll->VPhysicsGetObject();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wasn't able to ragdoll this target
|
||||
*pPhysObj = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Creates a dense ball with a mass equal to the aggregate mass consumed by the vortex
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGravityVortexController::CreateDenseBall( void )
|
||||
{
|
||||
CBaseEntity *pBall = CreateEntityByName( "prop_physics" );
|
||||
|
||||
pBall->SetModel( DENSE_BALL_MODEL );
|
||||
pBall->SetAbsOrigin( GetAbsOrigin() );
|
||||
pBall->Spawn();
|
||||
|
||||
IPhysicsObject *pObj = pBall->VPhysicsGetObject();
|
||||
if ( pObj != NULL )
|
||||
{
|
||||
pObj->SetMass( GetConsumedMass() );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Pulls physical objects towards the vortex center, killing them if they come too near
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGravityVortexController::PullThink( void )
|
||||
{
|
||||
// Pull any players close enough to us
|
||||
PullPlayersInRange();
|
||||
|
||||
Vector mins, maxs;
|
||||
mins = GetAbsOrigin() - Vector( m_flRadius, m_flRadius, m_flRadius );
|
||||
maxs = GetAbsOrigin() + Vector( m_flRadius, m_flRadius, m_flRadius );
|
||||
|
||||
// Draw debug information
|
||||
if ( g_debug_hopwire.GetBool() )
|
||||
{
|
||||
NDebugOverlay::Box( GetAbsOrigin(), mins - GetAbsOrigin(), maxs - GetAbsOrigin(), 0, 255, 0, 16, 4.0f );
|
||||
}
|
||||
|
||||
CBaseEntity *pEnts[128];
|
||||
int numEnts = UTIL_EntitiesInBox( pEnts, 128, mins, maxs, 0 );
|
||||
|
||||
for ( int i = 0; i < numEnts; i++ )
|
||||
{
|
||||
IPhysicsObject *pPhysObject = NULL;
|
||||
|
||||
// Attempt to kill and ragdoll any victims in range
|
||||
if ( KillNPCInRange( pEnts[i], &pPhysObject ) == false )
|
||||
{
|
||||
// If we didn't have a valid victim, see if we can just get the vphysics object
|
||||
pPhysObject = pEnts[i]->VPhysicsGetObject();
|
||||
if ( pPhysObject == NULL )
|
||||
continue;
|
||||
}
|
||||
|
||||
float mass;
|
||||
|
||||
CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pEnts[i] );
|
||||
if ( pRagdoll != NULL )
|
||||
{
|
||||
ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll();
|
||||
mass = 0.0f;
|
||||
|
||||
// Find the aggregate mass of the whole ragdoll
|
||||
for ( int j = 0; j < pRagdollPhys->listCount; ++j )
|
||||
{
|
||||
mass += pRagdollPhys->list[j].pObject->GetMass();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mass = pPhysObject->GetMass();
|
||||
}
|
||||
|
||||
Vector vecForce = GetAbsOrigin() - pEnts[i]->WorldSpaceCenter();
|
||||
Vector vecForce2D = vecForce;
|
||||
vecForce2D[2] = 0.0f;
|
||||
float dist2D = VectorNormalize( vecForce2D );
|
||||
float dist = VectorNormalize( vecForce );
|
||||
|
||||
// FIXME: Need a more deterministic method here
|
||||
if ( dist < 48.0f )
|
||||
{
|
||||
ConsumeEntity( pEnts[i] );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Must be within the radius
|
||||
if ( dist > m_flRadius )
|
||||
continue;
|
||||
|
||||
// Find the pull force
|
||||
vecForce *= ( 1.0f - ( dist2D / m_flRadius ) ) * m_flStrength * mass;
|
||||
|
||||
if ( pEnts[i]->VPhysicsGetObject() )
|
||||
{
|
||||
// Pull the object in
|
||||
pEnts[i]->VPhysicsTakeDamage( CTakeDamageInfo( this, this, vecForce, GetAbsOrigin(), m_flStrength, DMG_BLAST ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Keep going if need-be
|
||||
if ( m_flEndTime > gpGlobals->curtime )
|
||||
{
|
||||
SetThink( &CGravityVortexController::PullThink );
|
||||
SetNextThink( gpGlobals->curtime + 0.1f );
|
||||
}
|
||||
else
|
||||
{
|
||||
//Msg( "Consumed %.2f kilograms\n", m_flMass );
|
||||
//CreateDenseBall();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Starts the vortex working
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGravityVortexController::StartPull( const Vector &origin, float radius, float strength, float duration )
|
||||
{
|
||||
SetAbsOrigin( origin );
|
||||
m_flEndTime = gpGlobals->curtime + duration;
|
||||
m_flRadius = radius;
|
||||
m_flStrength= strength;
|
||||
|
||||
SetThink( &CGravityVortexController::PullThink );
|
||||
SetNextThink( gpGlobals->curtime + 0.1f );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Creation utility
|
||||
//-----------------------------------------------------------------------------
|
||||
CGravityVortexController *CGravityVortexController::Create( const Vector &origin, float radius, float strength, float duration )
|
||||
{
|
||||
// Create an instance of the vortex
|
||||
CGravityVortexController *pVortex = (CGravityVortexController *) CreateEntityByName( "vortex_controller" );
|
||||
if ( pVortex == NULL )
|
||||
return NULL;
|
||||
|
||||
// Start the vortex working
|
||||
pVortex->StartPull( origin, radius, strength, duration );
|
||||
|
||||
return pVortex;
|
||||
}
|
||||
|
||||
BEGIN_DATADESC( CGravityVortexController )
|
||||
DEFINE_FIELD( m_flMass, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flEndTime, FIELD_TIME ),
|
||||
DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flStrength, FIELD_FLOAT ),
|
||||
|
||||
DEFINE_THINKFUNC( PullThink ),
|
||||
END_DATADESC()
|
||||
|
||||
LINK_ENTITY_TO_CLASS( vortex_controller, CGravityVortexController );
|
||||
|
||||
#define GRENADE_MODEL_CLOSED "models/roller.mdl"
|
||||
#define GRENADE_MODEL_OPEN "models/roller_spikes.mdl"
|
||||
|
||||
BEGIN_DATADESC( CGrenadeHopwire )
|
||||
DEFINE_FIELD( m_hVortexController, FIELD_EHANDLE ),
|
||||
|
||||
DEFINE_THINKFUNC( EndThink ),
|
||||
DEFINE_THINKFUNC( CombatThink ),
|
||||
END_DATADESC()
|
||||
|
||||
LINK_ENTITY_TO_CLASS( npc_grenade_hopwire, CGrenadeHopwire );
|
||||
|
||||
IMPLEMENT_SERVERCLASS_ST( CGrenadeHopwire, DT_GrenadeHopwire )
|
||||
END_SEND_TABLE()
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGrenadeHopwire::Spawn( void )
|
||||
{
|
||||
Precache();
|
||||
|
||||
SetModel( GRENADE_MODEL_CLOSED );
|
||||
SetCollisionGroup( COLLISION_GROUP_PROJECTILE );
|
||||
|
||||
CreateVPhysics();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CGrenadeHopwire::CreateVPhysics()
|
||||
{
|
||||
// Create the object in the physics system
|
||||
VPhysicsInitNormal( SOLID_BBOX, 0, false );
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGrenadeHopwire::Precache( void )
|
||||
{
|
||||
// FIXME: Replace
|
||||
//PrecacheSound("NPC_Strider.Shoot");
|
||||
//PrecacheSound("d3_citadel.weapon_zapper_beam_loop2");
|
||||
|
||||
PrecacheModel( GRENADE_MODEL_OPEN );
|
||||
PrecacheModel( GRENADE_MODEL_CLOSED );
|
||||
|
||||
PrecacheModel( DENSE_BALL_MODEL );
|
||||
|
||||
BaseClass::Precache();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : timer -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGrenadeHopwire::SetTimer( float timer )
|
||||
{
|
||||
SetThink( &CBaseGrenade::PreDetonate );
|
||||
SetNextThink( gpGlobals->curtime + timer );
|
||||
}
|
||||
|
||||
#define MAX_STRIDER_KILL_DISTANCE_HORZ (hopwire_strider_kill_dist_h.GetFloat()) // Distance a Strider will be killed if within
|
||||
#define MAX_STRIDER_KILL_DISTANCE_VERT (hopwire_strider_kill_dist_v.GetFloat()) // Distance a Strider will be killed if within
|
||||
|
||||
#define MAX_STRIDER_STUN_DISTANCE_HORZ (MAX_STRIDER_KILL_DISTANCE_HORZ*2) // Distance a Strider will be stunned if within
|
||||
#define MAX_STRIDER_STUN_DISTANCE_VERT (MAX_STRIDER_KILL_DISTANCE_VERT*2) // Distance a Strider will be stunned if within
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGrenadeHopwire::KillStriders( void )
|
||||
{
|
||||
CBaseEntity *pEnts[128];
|
||||
Vector mins, maxs;
|
||||
|
||||
ClearBounds( mins, maxs );
|
||||
AddPointToBounds( -Vector( MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ ), mins, maxs );
|
||||
AddPointToBounds( Vector( MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ, MAX_STRIDER_STUN_DISTANCE_HORZ ), mins, maxs );
|
||||
AddPointToBounds( -Vector( MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT ), mins, maxs );
|
||||
AddPointToBounds( Vector( MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT, MAX_STRIDER_STUN_DISTANCE_VERT ), mins, maxs );
|
||||
|
||||
// FIXME: It's probably much faster to simply iterate over the striders in the map, rather than any entity in the radius - jdw
|
||||
|
||||
// Find any striders in range of us
|
||||
int numTargets = UTIL_EntitiesInBox( pEnts, ARRAYSIZE( pEnts ), GetAbsOrigin()+mins, GetAbsOrigin()+maxs, FL_NPC );
|
||||
float targetDistHorz, targetDistVert;
|
||||
|
||||
for ( int i = 0; i < numTargets; i++ )
|
||||
{
|
||||
// Only affect striders
|
||||
if ( FClassnameIs( pEnts[i], "npc_strider" ) == false )
|
||||
continue;
|
||||
|
||||
// We categorize our spatial relation to the strider in horizontal and vertical terms, so that we can specify both parameters separately
|
||||
targetDistHorz = UTIL_DistApprox2D( pEnts[i]->GetAbsOrigin(), GetAbsOrigin() );
|
||||
targetDistVert = fabs( pEnts[i]->GetAbsOrigin()[2] - GetAbsOrigin()[2] );
|
||||
|
||||
if ( targetDistHorz < MAX_STRIDER_KILL_DISTANCE_HORZ && targetDistHorz < MAX_STRIDER_KILL_DISTANCE_VERT )
|
||||
{
|
||||
// Kill the strider
|
||||
float fracDamage = ( pEnts[i]->GetMaxHealth() / hopwire_strider_hits.GetFloat() ) + 1.0f;
|
||||
CTakeDamageInfo killInfo( this, this, fracDamage, DMG_GENERIC );
|
||||
Vector killDir = pEnts[i]->GetAbsOrigin() - GetAbsOrigin();
|
||||
VectorNormalize( killDir );
|
||||
|
||||
killInfo.SetDamageForce( killDir * -1000.0f );
|
||||
killInfo.SetDamagePosition( GetAbsOrigin() );
|
||||
|
||||
pEnts[i]->TakeDamage( killInfo );
|
||||
}
|
||||
else if ( targetDistHorz < MAX_STRIDER_STUN_DISTANCE_HORZ && targetDistHorz < MAX_STRIDER_STUN_DISTANCE_VERT )
|
||||
{
|
||||
// Stun the strider
|
||||
CTakeDamageInfo killInfo( this, this, 200.0f, DMG_GENERIC );
|
||||
pEnts[i]->TakeDamage( killInfo );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGrenadeHopwire::EndThink( void )
|
||||
{
|
||||
if ( hopwire_vortex.GetBool() )
|
||||
{
|
||||
EntityMessageBegin( this, true );
|
||||
WRITE_BYTE( 1 );
|
||||
MessageEnd();
|
||||
}
|
||||
|
||||
SetThink( &CBaseEntity::SUB_Remove );
|
||||
SetNextThink( gpGlobals->curtime + 1.0f );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGrenadeHopwire::CombatThink( void )
|
||||
{
|
||||
// Stop the grenade from moving
|
||||
AddEFlags( EF_NODRAW );
|
||||
AddFlag( FSOLID_NOT_SOLID );
|
||||
VPhysicsDestroyObject();
|
||||
SetAbsVelocity( vec3_origin );
|
||||
SetMoveType( MOVETYPE_NONE );
|
||||
|
||||
// Do special behaviors if there are any striders in the area
|
||||
KillStriders();
|
||||
|
||||
// FIXME: Replace
|
||||
//EmitSound("NPC_Strider.Shoot");
|
||||
//EmitSound("d3_citadel.weapon_zapper_beam_loop2");
|
||||
|
||||
// Quick screen flash
|
||||
CBasePlayer *pPlayer = ToBasePlayer( GetThrower() );
|
||||
color32 white = { 255,255,255,255 };
|
||||
UTIL_ScreenFade( pPlayer, white, 0.2f, 0.0f, FFADE_IN );
|
||||
|
||||
// Create the vortex controller to pull entities towards us
|
||||
if ( hopwire_vortex.GetBool() )
|
||||
{
|
||||
m_hVortexController = CGravityVortexController::Create( GetAbsOrigin(), 512, 150, 3.0f );
|
||||
|
||||
// Start our client-side effect
|
||||
EntityMessageBegin( this, true );
|
||||
WRITE_BYTE( 0 );
|
||||
MessageEnd();
|
||||
|
||||
// Begin to stop in two seconds
|
||||
SetThink( &CGrenadeHopwire::EndThink );
|
||||
SetNextThink( gpGlobals->curtime + 2.0f );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove us immediately
|
||||
SetThink( &CBaseEntity::SUB_Remove );
|
||||
SetNextThink( gpGlobals->curtime + 0.1f );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGrenadeHopwire::SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity )
|
||||
{
|
||||
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
|
||||
|
||||
if ( pPhysicsObject != NULL )
|
||||
{
|
||||
pPhysicsObject->AddVelocity( &velocity, &angVelocity );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Hop off the ground to start deployment
|
||||
//-----------------------------------------------------------------------------
|
||||
void CGrenadeHopwire::Detonate( void )
|
||||
{
|
||||
SetModel( GRENADE_MODEL_OPEN );
|
||||
|
||||
AngularImpulse hopAngle = RandomAngularImpulse( -300, 300 );
|
||||
|
||||
//Find out how tall the ceiling is and always try to hop halfway
|
||||
trace_t tr;
|
||||
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, MAX_HOP_HEIGHT*2 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
|
||||
|
||||
// Jump half the height to the found ceiling
|
||||
float hopHeight = MIN( MAX_HOP_HEIGHT, (MAX_HOP_HEIGHT*tr.fraction) );
|
||||
|
||||
//Add upwards velocity for the "hop"
|
||||
Vector hopVel( 0.0f, 0.0f, hopHeight );
|
||||
SetVelocity( hopVel, hopAngle );
|
||||
|
||||
// Get the time until the apex of the hop
|
||||
float apexTime = sqrt( hopHeight / GetCurrentGravity() );
|
||||
|
||||
// Explode at the apex
|
||||
SetThink( &CGrenadeHopwire::CombatThink );
|
||||
SetNextThink( gpGlobals->curtime + apexTime);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
CBaseGrenade *HopWire_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer )
|
||||
{
|
||||
CGrenadeHopwire *pGrenade = (CGrenadeHopwire *) CBaseEntity::Create( "npc_grenade_hopwire", position, angles, pOwner );
|
||||
|
||||
// Only set ourselves to detonate on a timer if we're not a trap hopwire
|
||||
if ( hopwire_trap.GetBool() == false )
|
||||
{
|
||||
pGrenade->SetTimer( timer );
|
||||
}
|
||||
|
||||
pGrenade->SetVelocity( velocity, angVelocity );
|
||||
pGrenade->SetThrower( ToBaseCombatCharacter( pOwner ) );
|
||||
|
||||
return pGrenade;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef GRENADE_HOPWIRE_H
|
||||
#define GRENADE_HOPWIRE_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "basegrenade_shared.h"
|
||||
#include "Sprite.h"
|
||||
|
||||
extern ConVar hopwire_trap;
|
||||
|
||||
class CGravityVortexController;
|
||||
|
||||
class CGrenadeHopwire : public CBaseGrenade
|
||||
{
|
||||
DECLARE_CLASS( CGrenadeHopwire, CBaseGrenade );
|
||||
DECLARE_DATADESC();
|
||||
DECLARE_SERVERCLASS();
|
||||
|
||||
public:
|
||||
void Spawn( void );
|
||||
void Precache( void );
|
||||
bool CreateVPhysics( void );
|
||||
void SetTimer( float timer );
|
||||
void SetVelocity( const Vector &velocity, const AngularImpulse &angVelocity );
|
||||
void Detonate( void );
|
||||
|
||||
void EndThink( void ); // Last think before going away
|
||||
void CombatThink( void ); // Makes the main explosion go off
|
||||
|
||||
protected:
|
||||
|
||||
void KillStriders( void );
|
||||
|
||||
CHandle<CGravityVortexController> m_hVortexController;
|
||||
};
|
||||
|
||||
extern CBaseGrenade *HopWire_Create( const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, CBaseEntity *pOwner, float timer );
|
||||
|
||||
#endif // GRENADE_HOPWIRE_H
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Expose an IsAHunter function
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#ifndef NPC_HUNTER_H
|
||||
#define NPC_HUNTER_H
|
||||
|
||||
#if defined( _WIN32 )
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
class CBaseEntity;
|
||||
|
||||
/// true if given entity pointer is a hunter.
|
||||
bool Hunter_IsHunter(CBaseEntity *pEnt);
|
||||
|
||||
// call throughs for member functions
|
||||
|
||||
void Hunter_StriderBusterAttached( CBaseEntity *pHunter, CBaseEntity *pAttached );
|
||||
void Hunter_StriderBusterDetached( CBaseEntity *pHunter, CBaseEntity *pAttached );
|
||||
void Hunter_StriderBusterLaunched( CBaseEntity *pBuster );
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,127 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Dr. Magnusson, a grumpy bastard who builds satellites and rockets
|
||||
// at the White Forest missile silo. Instantly unlikeable, he is also
|
||||
// the inventor of the Magnusson Device aka "strider buster", which
|
||||
// is purported to resemble his cantelope-like head.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Generic NPC - purely for scripted sequence work.
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "cbase.h"
|
||||
#include "npcevent.h"
|
||||
#include "ai_basenpc.h"
|
||||
#include "ai_hull.h"
|
||||
#include "ai_baseactor.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// NPC's Anim Events Go Here
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
class CNPC_Magnusson : public CAI_BaseActor
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( CNPC_Magnusson, CAI_BaseActor );
|
||||
|
||||
void Spawn( void );
|
||||
void Precache( void );
|
||||
Class_T Classify ( void );
|
||||
void HandleAnimEvent( animevent_t *pEvent );
|
||||
int GetSoundInterests ( void );
|
||||
};
|
||||
|
||||
LINK_ENTITY_TO_CLASS( npc_magnusson, CNPC_Magnusson );
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Classify - indicates this NPC's place in the
|
||||
// relationship table.
|
||||
//-----------------------------------------------------------------------------
|
||||
Class_T CNPC_Magnusson::Classify ( void )
|
||||
{
|
||||
return CLASS_PLAYER_ALLY_VITAL;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// HandleAnimEvent - catches the NPC-specific messages
|
||||
// that occur when tagged animation frames are played.
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Magnusson::HandleAnimEvent( animevent_t *pEvent )
|
||||
{
|
||||
switch( pEvent->event )
|
||||
{
|
||||
case 1:
|
||||
default:
|
||||
BaseClass::HandleAnimEvent( pEvent );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// GetSoundInterests - generic NPC can't hear.
|
||||
//-----------------------------------------------------------------------------
|
||||
int CNPC_Magnusson::GetSoundInterests ( void )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Spawn
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Magnusson::Spawn()
|
||||
{
|
||||
// Allow custom model usage (mostly for monitors)
|
||||
char *szModel = (char *)STRING( GetModelName() );
|
||||
if (!szModel || !*szModel)
|
||||
{
|
||||
szModel = "models/magnusson.mdl";
|
||||
SetModelName( AllocPooledString(szModel) );
|
||||
}
|
||||
|
||||
Precache();
|
||||
SetModel( szModel );
|
||||
|
||||
BaseClass::Spawn();
|
||||
|
||||
SetHullType(HULL_HUMAN);
|
||||
SetHullSizeNormal();
|
||||
|
||||
SetSolid( SOLID_BBOX );
|
||||
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
||||
SetMoveType( MOVETYPE_STEP );
|
||||
SetBloodColor( BLOOD_COLOR_RED );
|
||||
m_iHealth = 8;
|
||||
m_flFieldOfView = 0.5;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
|
||||
m_NPCState = NPC_STATE_NONE;
|
||||
|
||||
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD );
|
||||
CapabilitiesAdd( bits_CAP_FRIENDLY_DMG_IMMUNE );
|
||||
|
||||
AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION );
|
||||
|
||||
NPCInit();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Precache - precaches all resources this NPC needs
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Magnusson::Precache()
|
||||
{
|
||||
PrecacheModel( STRING( GetModelName() ) );
|
||||
|
||||
BaseClass::Precache();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// AI Schedules Specific to this NPC
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -0,0 +1,121 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: NPC Puppet
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "cbase.h"
|
||||
#include "ai_basenpc.h"
|
||||
|
||||
// Must be the last file included
|
||||
#include "memdbgon.h"
|
||||
|
||||
class CNPC_Puppet : public CAI_BaseNPC
|
||||
{
|
||||
DECLARE_CLASS( CNPC_Puppet, CAI_BaseNPC );
|
||||
public:
|
||||
|
||||
virtual void Spawn( void );
|
||||
virtual void Precache( void );
|
||||
|
||||
void InputSetAnimationTarget( inputdata_t &inputdata );
|
||||
|
||||
private:
|
||||
|
||||
string_t m_sAnimTargetname;
|
||||
string_t m_sAnimAttachmentName;
|
||||
|
||||
CNetworkVar( EHANDLE, m_hAnimationTarget ); // NPC that will drive what animation we're playing
|
||||
CNetworkVar( int, m_nTargetAttachment ); // Attachment point to match to on the target
|
||||
|
||||
DECLARE_DATADESC();
|
||||
DECLARE_SERVERCLASS();
|
||||
};
|
||||
|
||||
LINK_ENTITY_TO_CLASS( npc_puppet, CNPC_Puppet );
|
||||
|
||||
BEGIN_DATADESC( CNPC_Puppet )
|
||||
DEFINE_KEYFIELD( m_sAnimTargetname, FIELD_STRING, "animationtarget" ),
|
||||
DEFINE_KEYFIELD( m_sAnimAttachmentName, FIELD_STRING, "attachmentname" ),
|
||||
|
||||
DEFINE_FIELD( m_nTargetAttachment, FIELD_INTEGER ),
|
||||
DEFINE_FIELD( m_hAnimationTarget, FIELD_EHANDLE ),
|
||||
DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimationTarget", InputSetAnimationTarget ),
|
||||
END_DATADESC()
|
||||
|
||||
IMPLEMENT_SERVERCLASS_ST( CNPC_Puppet, DT_NPC_Puppet )
|
||||
SendPropEHandle( SENDINFO( m_hAnimationTarget ) ),
|
||||
SendPropInt( SENDINFO( m_nTargetAttachment) ),
|
||||
END_SEND_TABLE()
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Puppet::Precache( void )
|
||||
{
|
||||
BaseClass::Precache();
|
||||
PrecacheModel( STRING( GetModelName() ) );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Puppet::Spawn( void )
|
||||
{
|
||||
BaseClass::Spawn();
|
||||
|
||||
Precache();
|
||||
|
||||
SetModel( STRING( GetModelName() ) );
|
||||
|
||||
NPCInit();
|
||||
|
||||
SetHealth( 100 );
|
||||
|
||||
// Find our animation target
|
||||
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_sAnimTargetname );
|
||||
m_hAnimationTarget = pTarget;
|
||||
if ( pTarget )
|
||||
{
|
||||
CBaseAnimating *pAnimating = pTarget->GetBaseAnimating();
|
||||
if ( pAnimating )
|
||||
{
|
||||
m_nTargetAttachment = pAnimating->LookupAttachment( STRING( m_sAnimAttachmentName ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Always be scripted
|
||||
SetInAScript( true );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : &inputdata -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNPC_Puppet::InputSetAnimationTarget( inputdata_t &inputdata )
|
||||
{
|
||||
// Take the new name
|
||||
m_sAnimTargetname = MAKE_STRING( inputdata.value.String() );
|
||||
|
||||
// Find our animation target
|
||||
CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_sAnimTargetname );
|
||||
if ( pTarget == NULL )
|
||||
{
|
||||
Warning("Failed to find animation target %s for npc_puppet (%s)\n", STRING( m_sAnimTargetname ), STRING( GetEntityName() ) );
|
||||
return;
|
||||
}
|
||||
|
||||
m_hAnimationTarget = pTarget;
|
||||
|
||||
CBaseAnimating *pAnimating = pTarget->GetBaseAnimating();
|
||||
if ( pAnimating )
|
||||
{
|
||||
// Cache off our target attachment
|
||||
m_nTargetAttachment = pAnimating->LookupAttachment( STRING( m_sAnimAttachmentName ) );
|
||||
}
|
||||
|
||||
// Stuff us at the owner's core for visibility reasons
|
||||
SetParent( pTarget );
|
||||
SetLocalOrigin( vec3_origin );
|
||||
SetLocalAngles( vec3_angle );
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose: Big pulsating ball inside the core of the citadel
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "baseentity.h"
|
||||
|
||||
#define COREBALL_MODEL "models/props_combine/coreball.mdl"
|
||||
|
||||
class CPropScalable : public CBaseAnimating
|
||||
{
|
||||
public:
|
||||
DECLARE_CLASS( CPropScalable, CBaseAnimating );
|
||||
DECLARE_DATADESC();
|
||||
DECLARE_SERVERCLASS();
|
||||
|
||||
CPropScalable();
|
||||
|
||||
virtual void Spawn( void );
|
||||
virtual void Precache( void );
|
||||
|
||||
CNetworkVar( float, m_flScaleX );
|
||||
CNetworkVar( float, m_flScaleY );
|
||||
CNetworkVar( float, m_flScaleZ );
|
||||
|
||||
CNetworkVar( float, m_flLerpTimeX );
|
||||
CNetworkVar( float, m_flLerpTimeY );
|
||||
CNetworkVar( float, m_flLerpTimeZ );
|
||||
|
||||
CNetworkVar( float, m_flGoalTimeX );
|
||||
CNetworkVar( float, m_flGoalTimeY );
|
||||
CNetworkVar( float, m_flGoalTimeZ );
|
||||
|
||||
void InputSetScaleX( inputdata_t &inputdata );
|
||||
void InputSetScaleY( inputdata_t &inputdata );
|
||||
void InputSetScaleZ( inputdata_t &inputdata );
|
||||
};
|
||||
|
||||
LINK_ENTITY_TO_CLASS( prop_coreball, CPropScalable );
|
||||
LINK_ENTITY_TO_CLASS( prop_scalable, CPropScalable );
|
||||
|
||||
BEGIN_DATADESC( CPropScalable )
|
||||
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleX", InputSetScaleX ),
|
||||
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleY", InputSetScaleY ),
|
||||
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetScaleZ", InputSetScaleZ ),
|
||||
|
||||
DEFINE_FIELD( m_flScaleX, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flScaleY, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flScaleZ, FIELD_FLOAT ),
|
||||
|
||||
DEFINE_FIELD( m_flLerpTimeX, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flLerpTimeY, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flLerpTimeZ, FIELD_FLOAT ),
|
||||
|
||||
DEFINE_FIELD( m_flGoalTimeX, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flGoalTimeY, FIELD_FLOAT ),
|
||||
DEFINE_FIELD( m_flGoalTimeZ, FIELD_FLOAT ),
|
||||
END_DATADESC()
|
||||
|
||||
IMPLEMENT_SERVERCLASS_ST( CPropScalable, DT_PropScalable )
|
||||
SendPropFloat( SENDINFO(m_flScaleX), 0, SPROP_NOSCALE ),
|
||||
SendPropFloat( SENDINFO(m_flScaleY), 0, SPROP_NOSCALE ),
|
||||
SendPropFloat( SENDINFO(m_flScaleZ), 0, SPROP_NOSCALE ),
|
||||
|
||||
SendPropFloat( SENDINFO(m_flLerpTimeX), 0, SPROP_NOSCALE ),
|
||||
SendPropFloat( SENDINFO(m_flLerpTimeY), 0, SPROP_NOSCALE ),
|
||||
SendPropFloat( SENDINFO(m_flLerpTimeZ), 0, SPROP_NOSCALE ),
|
||||
|
||||
SendPropFloat( SENDINFO(m_flGoalTimeX), 0, SPROP_NOSCALE ),
|
||||
SendPropFloat( SENDINFO(m_flGoalTimeY), 0, SPROP_NOSCALE ),
|
||||
SendPropFloat( SENDINFO(m_flGoalTimeZ), 0, SPROP_NOSCALE ),
|
||||
END_SEND_TABLE()
|
||||
|
||||
CPropScalable::CPropScalable( void )
|
||||
{
|
||||
m_flScaleX = 1.0f;
|
||||
m_flScaleY = 1.0f;
|
||||
m_flScaleZ = 1.0f;
|
||||
|
||||
UseClientSideAnimation();
|
||||
}
|
||||
|
||||
void CPropScalable::Spawn( void )
|
||||
{
|
||||
// Stomp our model name if we're the coreball (legacy)
|
||||
if ( FClassnameIs( this, "prop_coreball" ) )
|
||||
{
|
||||
PrecacheModel( COREBALL_MODEL );
|
||||
SetModel( COREBALL_MODEL );
|
||||
}
|
||||
else
|
||||
{
|
||||
char *szModel = (char *)STRING( GetModelName() );
|
||||
if (!szModel || !*szModel)
|
||||
{
|
||||
Warning( "prop_scalable at %.0f %.0f %0.f missing modelname\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
|
||||
UTIL_Remove( this );
|
||||
return;
|
||||
}
|
||||
|
||||
PrecacheModel( szModel );
|
||||
SetModel( szModel );
|
||||
}
|
||||
|
||||
SetMoveType( MOVETYPE_NONE );
|
||||
|
||||
BaseClass::Spawn();
|
||||
|
||||
AddEffects( EF_NOSHADOW );
|
||||
|
||||
SetSequence( 0 );
|
||||
SetPlaybackRate( 1.0f );
|
||||
}
|
||||
|
||||
void CPropScalable::Precache( void )
|
||||
{
|
||||
BaseClass::Precache();
|
||||
}
|
||||
|
||||
void CPropScalable::InputSetScaleX( inputdata_t &inputdata )
|
||||
{
|
||||
Vector vecScale;
|
||||
inputdata.value.Vector3D( vecScale );
|
||||
|
||||
m_flScaleX = vecScale.x;
|
||||
m_flLerpTimeX = vecScale.y;
|
||||
m_flGoalTimeX = gpGlobals->curtime;
|
||||
}
|
||||
|
||||
void CPropScalable::InputSetScaleY( inputdata_t &inputdata )
|
||||
{
|
||||
Vector vecScale;
|
||||
inputdata.value.Vector3D( vecScale );
|
||||
|
||||
m_flScaleY = vecScale.x;
|
||||
m_flLerpTimeY = vecScale.y;
|
||||
m_flGoalTimeY = gpGlobals->curtime;
|
||||
}
|
||||
|
||||
void CPropScalable::InputSetScaleZ( inputdata_t &inputdata )
|
||||
{
|
||||
Vector vecScale;
|
||||
inputdata.value.Vector3D( vecScale );
|
||||
|
||||
m_flScaleZ = vecScale.x;
|
||||
m_flLerpTimeZ = vecScale.y;
|
||||
m_flGoalTimeZ = gpGlobals->curtime;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef VEHICLE_JEEP_EPISODIC_H
|
||||
#define VEHICLE_JEEP_EPISODIC_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include "vehicle_jeep.h"
|
||||
#include "ai_basenpc.h"
|
||||
#include "hl2_vehicle_radar.h"
|
||||
|
||||
class CParticleSystem;
|
||||
class CVehicleCargoTrigger;
|
||||
class CSprite;
|
||||
|
||||
#define NUM_WHEEL_EFFECTS 2
|
||||
#define NUM_HAZARD_LIGHTS 4
|
||||
|
||||
//=============================================================================
|
||||
// Episodic jeep
|
||||
|
||||
class CPropJeepEpisodic : public CPropJeep
|
||||
{
|
||||
DECLARE_CLASS( CPropJeepEpisodic, CPropJeep );
|
||||
DECLARE_SERVERCLASS();
|
||||
|
||||
public:
|
||||
CPropJeepEpisodic( void );
|
||||
|
||||
virtual void Spawn( void );
|
||||
virtual void Activate( void );
|
||||
virtual void Think( void );
|
||||
virtual void UpdateOnRemove( void );
|
||||
|
||||
virtual void NPC_FinishedEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
|
||||
virtual void NPC_FinishedExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
|
||||
|
||||
virtual bool NPC_CanEnterVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
|
||||
virtual bool NPC_CanExitVehicle( CAI_BaseNPC *pPassenger, bool bCompanion );
|
||||
virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
|
||||
virtual void Precache( void );
|
||||
virtual void EnterVehicle( CBaseCombatCharacter *pPassenger );
|
||||
virtual void ExitVehicle( int nRole );
|
||||
virtual bool AllowBlockedExit( CBaseCombatCharacter *pPassenger, int nRole );
|
||||
|
||||
// Passengers take no damage except what we pass them
|
||||
virtual bool PassengerShouldReceiveDamage( CTakeDamageInfo &info )
|
||||
{
|
||||
if ( GetServerVehicle() && GetServerVehicle()->IsPassengerExiting() )
|
||||
return false;
|
||||
|
||||
return ( info.GetDamageType() & DMG_VEHICLE ) != 0;
|
||||
}
|
||||
|
||||
virtual int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_NOTIFY_ON_TRANSITION); }
|
||||
|
||||
void SpawnRadarPanel();
|
||||
void DestroyRadarPanel();
|
||||
int NumRadarContacts() { return m_iNumRadarContacts; }
|
||||
|
||||
void AddPropToCargoHold( CPhysicsProp *pProp );
|
||||
|
||||
virtual CBaseEntity *OnFailedPhysGunPickup( Vector vPhysgunPos );
|
||||
virtual void DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iButtonsDown, int iButtonsReleased );
|
||||
virtual int DrawDebugTextOverlays( void );
|
||||
|
||||
DECLARE_DATADESC();
|
||||
|
||||
protected:
|
||||
void HazardBlinkThink( void );
|
||||
void CreateHazardLights( void );
|
||||
void DestroyHazardLights( void );
|
||||
|
||||
void UpdateCargoEntry( void );
|
||||
void ReleasePropFromCargoHold( void );
|
||||
void CreateCargoTrigger( void );
|
||||
virtual float GetUprightTime( void ) { return 1.0f; }
|
||||
virtual float GetUprightStrength( void );
|
||||
virtual bool ShouldPuntUseLaunchForces( PhysGunForce_t reason ) { return ( reason == PHYSGUN_FORCE_PUNTED ); }
|
||||
virtual void HandleWater( void );
|
||||
|
||||
virtual AngularImpulse PhysGunLaunchAngularImpulse( void );
|
||||
virtual Vector PhysGunLaunchVelocity( const Vector &forward, float flMass );
|
||||
bool PassengerInTransition( void );
|
||||
|
||||
void SetBusterHopperVisibility(bool visible);
|
||||
|
||||
private:
|
||||
|
||||
void UpdateWheelDust( void );
|
||||
void UpdateRadar( bool forceUpdate = false );
|
||||
|
||||
void InputLockEntrance( inputdata_t &data );
|
||||
void InputUnlockEntrance( inputdata_t &data );
|
||||
void InputLockExit( inputdata_t &data );
|
||||
void InputUnlockExit( inputdata_t &data );
|
||||
void InputEnableRadar( inputdata_t &data );
|
||||
void InputDisableRadar( inputdata_t &data );
|
||||
void InputEnableRadarDetectEnemies( inputdata_t &data );
|
||||
void InputAddBusterToCargo( inputdata_t &data );
|
||||
void InputSetCargoVisibility( inputdata_t &data );
|
||||
void InputOutsideTransition( inputdata_t &data );
|
||||
void InputDisablePhysGun( inputdata_t &data );
|
||||
void InputEnablePhysGun( inputdata_t &data );
|
||||
void InputCreateLinkController( inputdata_t &data );
|
||||
void InputDestroyLinkController( inputdata_t &data );
|
||||
void CreateAvoidanceZone( void );
|
||||
|
||||
bool m_bEntranceLocked;
|
||||
bool m_bExitLocked;
|
||||
bool m_bAddingCargo;
|
||||
bool m_bBlink;
|
||||
|
||||
float m_flCargoStartTime; // Time when the cargo was first added to the vehicle (used for animating into hold)
|
||||
float m_flNextAvoidBroadcastTime; // Next time we'll warn entity to move out of us
|
||||
|
||||
COutputEvent m_OnCompanionEnteredVehicle; // Passenger has completed entering the vehicle
|
||||
COutputEvent m_OnCompanionExitedVehicle; // Passenger has completed exited the vehicle
|
||||
COutputEvent m_OnHostileEnteredVehicle; // Passenger has completed entering the vehicle
|
||||
COutputEvent m_OnHostileExitedVehicle; // Passenger has completed exited the vehicle
|
||||
|
||||
CHandle< CParticleSystem > m_hWheelDust[NUM_WHEEL_EFFECTS];
|
||||
CHandle< CParticleSystem > m_hWheelWater[NUM_WHEEL_EFFECTS];
|
||||
CHandle< CVehicleCargoTrigger > m_hCargoTrigger;
|
||||
CHandle< CPhysicsProp > m_hCargoProp;
|
||||
|
||||
CHandle< CSprite > m_hHazardLights[NUM_HAZARD_LIGHTS];
|
||||
float m_flNextWaterSound;
|
||||
|
||||
bool m_bRadarEnabled;
|
||||
bool m_bRadarDetectsEnemies;
|
||||
float m_flNextRadarUpdateTime;
|
||||
EHANDLE m_hRadarScreen;
|
||||
|
||||
EHANDLE m_hLinkControllerFront;
|
||||
EHANDLE m_hLinkControllerRear;
|
||||
|
||||
bool m_bBusterHopperVisible; // is the hopper assembly visible on the vehicle? please do not set this directly - use the accessor funct.
|
||||
|
||||
CNetworkVar( int, m_iNumRadarContacts );
|
||||
CNetworkArray( Vector, m_vecRadarContactPos, RADAR_MAX_CONTACTS );
|
||||
CNetworkArray( int, m_iRadarContactType, RADAR_MAX_CONTACTS );
|
||||
};
|
||||
|
||||
#endif // VEHICLE_JEEP_EPISODIC_H
|
||||
@@ -0,0 +1,508 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================//
|
||||
|
||||
#include "cbase.h"
|
||||
#include "basehlcombatweapon.h"
|
||||
#include "player.h"
|
||||
#include "gamerules.h"
|
||||
#include "grenade_frag.h"
|
||||
#include "npcevent.h"
|
||||
#include "engine/IEngineSound.h"
|
||||
#include "items.h"
|
||||
#include "in_buttons.h"
|
||||
#include "soundent.h"
|
||||
#include "grenade_hopwire.h"
|
||||
|
||||
// memdbgon must be the last include file in a .cpp file!!!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
#define GRENADE_TIMER 2.0f //Seconds
|
||||
|
||||
#define GRENADE_PAUSED_NO 0
|
||||
#define GRENADE_PAUSED_PRIMARY 1
|
||||
#define GRENADE_PAUSED_SECONDARY 2
|
||||
|
||||
#define GRENADE_RADIUS 4.0f // inches
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Fragmentation grenades
|
||||
//-----------------------------------------------------------------------------
|
||||
class CWeaponHopwire: public CBaseHLCombatWeapon
|
||||
{
|
||||
DECLARE_CLASS( CWeaponHopwire, CBaseHLCombatWeapon );
|
||||
public:
|
||||
DECLARE_SERVERCLASS();
|
||||
|
||||
void Precache( void );
|
||||
void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
|
||||
void PrimaryAttack( void );
|
||||
void SecondaryAttack( void );
|
||||
void DecrementAmmo( CBaseCombatCharacter *pOwner );
|
||||
void ItemPostFrame( void );
|
||||
|
||||
void HandleFireOnEmpty( void );
|
||||
bool HasAnyAmmo( void );
|
||||
bool Deploy( void );
|
||||
bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
|
||||
|
||||
int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; }
|
||||
|
||||
bool Reload( void );
|
||||
|
||||
private:
|
||||
void ThrowGrenade( CBasePlayer *pPlayer );
|
||||
void RollGrenade( CBasePlayer *pPlayer );
|
||||
void LobGrenade( CBasePlayer *pPlayer );
|
||||
// check a throw from vecSrc. If not valid, move the position back along the line to vecEye
|
||||
void CheckThrowPosition( CBasePlayer *pPlayer, const Vector &vecEye, Vector &vecSrc );
|
||||
|
||||
bool m_bRedraw; //Draw the weapon again after throwing a grenade
|
||||
|
||||
int m_AttackPaused;
|
||||
bool m_fDrawbackFinished;
|
||||
|
||||
CHandle<CGrenadeHopwire> m_hActiveHopWire;
|
||||
|
||||
DECLARE_ACTTABLE();
|
||||
|
||||
DECLARE_DATADESC();
|
||||
};
|
||||
|
||||
|
||||
BEGIN_DATADESC( CWeaponHopwire )
|
||||
DEFINE_FIELD( m_bRedraw, FIELD_BOOLEAN ),
|
||||
DEFINE_FIELD( m_AttackPaused, FIELD_INTEGER ),
|
||||
DEFINE_FIELD( m_fDrawbackFinished, FIELD_BOOLEAN ),
|
||||
DEFINE_FIELD( m_hActiveHopWire, FIELD_EHANDLE ),
|
||||
END_DATADESC()
|
||||
|
||||
acttable_t CWeaponHopwire::m_acttable[] =
|
||||
{
|
||||
{ ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true },
|
||||
};
|
||||
|
||||
IMPLEMENT_ACTTABLE(CWeaponHopwire);
|
||||
|
||||
IMPLEMENT_SERVERCLASS_ST(CWeaponHopwire, DT_WeaponHopwire)
|
||||
END_SEND_TABLE()
|
||||
|
||||
LINK_ENTITY_TO_CLASS( weapon_hopwire, CWeaponHopwire );
|
||||
PRECACHE_WEAPON_REGISTER(weapon_hopwire);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::Precache( void )
|
||||
{
|
||||
BaseClass::Precache();
|
||||
|
||||
UTIL_PrecacheOther( "npc_grenade_hopwire" );
|
||||
|
||||
PrecacheScriptSound( "WeaponFrag.Throw" );
|
||||
PrecacheScriptSound( "WeaponFrag.Roll" );
|
||||
|
||||
m_bRedraw = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWeaponHopwire::Deploy( void )
|
||||
{
|
||||
m_bRedraw = false;
|
||||
m_fDrawbackFinished = false;
|
||||
|
||||
return BaseClass::Deploy();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWeaponHopwire::Holster( CBaseCombatWeapon *pSwitchingTo )
|
||||
{
|
||||
if ( m_hActiveHopWire != NULL )
|
||||
return false;
|
||||
|
||||
m_bRedraw = false;
|
||||
m_fDrawbackFinished = false;
|
||||
|
||||
return BaseClass::Holster( pSwitchingTo );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *pEvent -
|
||||
// *pOperator -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
|
||||
{
|
||||
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
||||
bool fThrewGrenade = false;
|
||||
|
||||
switch( pEvent->event )
|
||||
{
|
||||
case EVENT_WEAPON_SEQUENCE_FINISHED:
|
||||
m_fDrawbackFinished = true;
|
||||
break;
|
||||
|
||||
case EVENT_WEAPON_THROW:
|
||||
ThrowGrenade( pOwner );
|
||||
DecrementAmmo( pOwner );
|
||||
fThrewGrenade = true;
|
||||
break;
|
||||
|
||||
case EVENT_WEAPON_THROW2:
|
||||
RollGrenade( pOwner );
|
||||
DecrementAmmo( pOwner );
|
||||
fThrewGrenade = true;
|
||||
break;
|
||||
|
||||
case EVENT_WEAPON_THROW3:
|
||||
LobGrenade( pOwner );
|
||||
DecrementAmmo( pOwner );
|
||||
fThrewGrenade = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
BaseClass::Operator_HandleAnimEvent( pEvent, pOperator );
|
||||
break;
|
||||
}
|
||||
|
||||
#define RETHROW_DELAY 0.5
|
||||
if( fThrewGrenade )
|
||||
{
|
||||
m_flNextPrimaryAttack = gpGlobals->curtime + RETHROW_DELAY;
|
||||
m_flNextSecondaryAttack = gpGlobals->curtime + RETHROW_DELAY;
|
||||
m_flTimeWeaponIdle = FLT_MAX; //NOTE: This is set once the animation has finished up!
|
||||
|
||||
// Make a sound designed to scare snipers back into their holes!
|
||||
CBaseCombatCharacter *pOwner = GetOwner();
|
||||
|
||||
if( pOwner )
|
||||
{
|
||||
Vector vecSrc = pOwner->Weapon_ShootPosition();
|
||||
Vector vecDir;
|
||||
|
||||
AngleVectors( pOwner->EyeAngles(), &vecDir );
|
||||
|
||||
trace_t tr;
|
||||
|
||||
UTIL_TraceLine( vecSrc, vecSrc + vecDir * 1024, MASK_SOLID_BRUSHONLY, pOwner, COLLISION_GROUP_NONE, &tr );
|
||||
|
||||
CSoundEnt::InsertSound( SOUND_DANGER_SNIPERONLY, tr.endpos, 384, 0.2, pOwner );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Override the ammo behavior so we never disallow pulling the weapon out
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWeaponHopwire::HasAnyAmmo( void )
|
||||
{
|
||||
if ( m_hActiveHopWire != NULL )
|
||||
return true;
|
||||
|
||||
return BaseClass::HasAnyAmmo();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Output : Returns true on success, false on failure.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CWeaponHopwire::Reload( void )
|
||||
{
|
||||
if ( !HasPrimaryAmmo() )
|
||||
return false;
|
||||
|
||||
if ( ( m_bRedraw ) && ( m_flNextPrimaryAttack <= gpGlobals->curtime ) && ( m_flNextSecondaryAttack <= gpGlobals->curtime ) )
|
||||
{
|
||||
//Redraw the weapon
|
||||
SendWeaponAnim( ACT_VM_DRAW );
|
||||
|
||||
//Update our times
|
||||
m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration();
|
||||
m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration();
|
||||
m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration();
|
||||
|
||||
//Mark this as done
|
||||
m_bRedraw = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::SecondaryAttack( void )
|
||||
{
|
||||
/*
|
||||
if ( m_bRedraw )
|
||||
return;
|
||||
|
||||
if ( !HasPrimaryAmmo() )
|
||||
return;
|
||||
|
||||
CBaseCombatCharacter *pOwner = GetOwner();
|
||||
|
||||
if ( pOwner == NULL )
|
||||
return;
|
||||
|
||||
CBasePlayer *pPlayer = ToBasePlayer( pOwner );
|
||||
|
||||
if ( pPlayer == NULL )
|
||||
return;
|
||||
|
||||
// Note that this is a secondary attack and prepare the grenade attack to pause.
|
||||
m_AttackPaused = GRENADE_PAUSED_SECONDARY;
|
||||
SendWeaponAnim( ACT_VM_PULLBACK_LOW );
|
||||
|
||||
// Don't let weapon idle interfere in the middle of a throw!
|
||||
m_flTimeWeaponIdle = FLT_MAX;
|
||||
m_flNextSecondaryAttack = FLT_MAX;
|
||||
|
||||
// If I'm now out of ammo, switch away
|
||||
if ( !HasPrimaryAmmo() )
|
||||
{
|
||||
pPlayer->SwitchToNextBestWeapon( this );
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Allow activation even if this is our last piece of ammo
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::HandleFireOnEmpty( void )
|
||||
{
|
||||
if ( m_hActiveHopWire!= NULL )
|
||||
{
|
||||
// FIXME: This toggle is hokey
|
||||
m_bRedraw = false;
|
||||
PrimaryAttack();
|
||||
m_bRedraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::PrimaryAttack( void )
|
||||
{
|
||||
if ( m_bRedraw )
|
||||
return;
|
||||
|
||||
CBaseCombatCharacter *pOwner = GetOwner();
|
||||
if ( pOwner == NULL )
|
||||
return;
|
||||
|
||||
CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );;
|
||||
if ( !pPlayer )
|
||||
return;
|
||||
|
||||
// See if we're in trap mode
|
||||
if ( hopwire_trap.GetBool() && ( m_hActiveHopWire != NULL ) )
|
||||
{
|
||||
// Spring the trap
|
||||
m_hActiveHopWire->Detonate();
|
||||
m_hActiveHopWire = NULL;
|
||||
|
||||
// Don't allow another throw for awhile
|
||||
m_flTimeWeaponIdle = m_flNextPrimaryAttack = gpGlobals->curtime + 2.0f;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that this is a primary attack and prepare the grenade attack to pause.
|
||||
/*
|
||||
m_AttackPaused = GRENADE_PAUSED_PRIMARY;
|
||||
SendWeaponAnim( ACT_VM_PULLBACK_HIGH );
|
||||
*/
|
||||
m_AttackPaused = GRENADE_PAUSED_SECONDARY;
|
||||
SendWeaponAnim( ACT_VM_PULLBACK_LOW );
|
||||
|
||||
// Put both of these off indefinitely. We do not know how long
|
||||
// the player will hold the grenade.
|
||||
m_flTimeWeaponIdle = FLT_MAX;
|
||||
m_flNextPrimaryAttack = FLT_MAX;
|
||||
|
||||
// If I'm now out of ammo, switch away
|
||||
/*
|
||||
if ( !HasPrimaryAmmo() )
|
||||
{
|
||||
pPlayer->SwitchToNextBestWeapon( this );
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *pOwner -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::DecrementAmmo( CBaseCombatCharacter *pOwner )
|
||||
{
|
||||
pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::ItemPostFrame( void )
|
||||
{
|
||||
if( m_fDrawbackFinished )
|
||||
{
|
||||
CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
|
||||
|
||||
if (pOwner)
|
||||
{
|
||||
switch( m_AttackPaused )
|
||||
{
|
||||
case GRENADE_PAUSED_PRIMARY:
|
||||
if( !(pOwner->m_nButtons & IN_ATTACK) )
|
||||
{
|
||||
SendWeaponAnim( ACT_VM_THROW );
|
||||
m_fDrawbackFinished = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case GRENADE_PAUSED_SECONDARY:
|
||||
if( !(pOwner->m_nButtons & (IN_ATTACK|IN_ATTACK2)) )
|
||||
{
|
||||
//See if we're ducking
|
||||
if ( pOwner->m_nButtons & IN_DUCK )
|
||||
{
|
||||
//Send the weapon animation
|
||||
SendWeaponAnim( ACT_VM_SECONDARYATTACK );
|
||||
}
|
||||
else
|
||||
{
|
||||
//Send the weapon animation
|
||||
SendWeaponAnim( ACT_VM_HAULBACK );
|
||||
}
|
||||
|
||||
m_fDrawbackFinished = false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BaseClass::ItemPostFrame();
|
||||
|
||||
if ( m_bRedraw )
|
||||
{
|
||||
if ( IsViewModelSequenceFinished() )
|
||||
{
|
||||
Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check a throw from vecSrc. If not valid, move the position back along the line to vecEye
|
||||
void CWeaponHopwire::CheckThrowPosition( CBasePlayer *pPlayer, const Vector &vecEye, Vector &vecSrc )
|
||||
{
|
||||
trace_t tr;
|
||||
|
||||
UTIL_TraceHull( vecEye, vecSrc, -Vector(GRENADE_RADIUS+2,GRENADE_RADIUS+2,GRENADE_RADIUS+2), Vector(GRENADE_RADIUS+2,GRENADE_RADIUS+2,GRENADE_RADIUS+2),
|
||||
pPlayer->PhysicsSolidMaskForEntity(), pPlayer, pPlayer->GetCollisionGroup(), &tr );
|
||||
|
||||
if ( tr.DidHit() )
|
||||
{
|
||||
vecSrc = tr.endpos;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *pPlayer -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::ThrowGrenade( CBasePlayer *pPlayer )
|
||||
{
|
||||
Vector vecEye = pPlayer->EyePosition();
|
||||
Vector vForward, vRight;
|
||||
|
||||
pPlayer->EyeVectors( &vForward, &vRight, NULL );
|
||||
Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f;
|
||||
CheckThrowPosition( pPlayer, vecEye, vecSrc );
|
||||
vForward[2] += 0.1f;
|
||||
|
||||
Vector vecThrow;
|
||||
pPlayer->GetVelocity( &vecThrow, NULL );
|
||||
vecThrow += vForward * 1200;
|
||||
m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, vec3_angle, vecThrow, AngularImpulse(600,random->RandomInt(-1200,1200),0), pPlayer, GRENADE_TIMER ));
|
||||
|
||||
m_bRedraw = true;
|
||||
|
||||
WeaponSound( SINGLE );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *pPlayer -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::LobGrenade( CBasePlayer *pPlayer )
|
||||
{
|
||||
Vector vecEye = pPlayer->EyePosition();
|
||||
Vector vForward, vRight;
|
||||
|
||||
pPlayer->EyeVectors( &vForward, &vRight, NULL );
|
||||
Vector vecSrc = vecEye + vForward * 18.0f + vRight * 8.0f + Vector( 0, 0, -8 );
|
||||
CheckThrowPosition( pPlayer, vecEye, vecSrc );
|
||||
|
||||
Vector vecThrow;
|
||||
pPlayer->GetVelocity( &vecThrow, NULL );
|
||||
vecThrow += vForward * 350 + Vector( 0, 0, 50 );
|
||||
m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, vec3_angle, vecThrow, AngularImpulse(200,random->RandomInt(-600,600),0), pPlayer, GRENADE_TIMER ));
|
||||
|
||||
WeaponSound( WPN_DOUBLE );
|
||||
|
||||
m_bRedraw = true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose:
|
||||
// Input : *pPlayer -
|
||||
//-----------------------------------------------------------------------------
|
||||
void CWeaponHopwire::RollGrenade( CBasePlayer *pPlayer )
|
||||
{
|
||||
// BUGBUG: Hardcoded grenade width of 4 - better not change the model :)
|
||||
Vector vecSrc;
|
||||
pPlayer->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecSrc );
|
||||
vecSrc.z += GRENADE_RADIUS;
|
||||
|
||||
Vector vecFacing = pPlayer->BodyDirection2D( );
|
||||
// no up/down direction
|
||||
vecFacing.z = 0;
|
||||
VectorNormalize( vecFacing );
|
||||
trace_t tr;
|
||||
UTIL_TraceLine( vecSrc, vecSrc - Vector(0,0,16), MASK_PLAYERSOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
|
||||
if ( tr.fraction != 1.0 )
|
||||
{
|
||||
// compute forward vec parallel to floor plane and roll grenade along that
|
||||
Vector tangent;
|
||||
CrossProduct( vecFacing, tr.plane.normal, tangent );
|
||||
CrossProduct( tr.plane.normal, tangent, vecFacing );
|
||||
}
|
||||
vecSrc += (vecFacing * 18.0);
|
||||
CheckThrowPosition( pPlayer, pPlayer->WorldSpaceCenter(), vecSrc );
|
||||
|
||||
Vector vecThrow;
|
||||
pPlayer->GetVelocity( &vecThrow, NULL );
|
||||
vecThrow += vecFacing * 700;
|
||||
// put it on its side
|
||||
QAngle orientation(0,pPlayer->GetLocalAngles().y,-90);
|
||||
// roll it
|
||||
AngularImpulse rotSpeed(0,0,720);
|
||||
m_hActiveHopWire = static_cast<CGrenadeHopwire *> (HopWire_Create( vecSrc, orientation, vecThrow, rotSpeed, pPlayer, GRENADE_TIMER ));
|
||||
|
||||
WeaponSound( SPECIAL1 );
|
||||
|
||||
m_bRedraw = true;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "cbase.h"
|
||||
#include "weapon_citizenpackage.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Old Man Harpoon - Lost Coast.
|
||||
//-----------------------------------------------------------------------------
|
||||
class CWeaponOldManHarpoon : public CWeaponCitizenPackage
|
||||
{
|
||||
DECLARE_CLASS( CWeaponOldManHarpoon, CWeaponCitizenPackage );
|
||||
public:
|
||||
DECLARE_SERVERCLASS();
|
||||
DECLARE_DATADESC();
|
||||
DECLARE_ACTTABLE();
|
||||
};
|
||||
|
||||
IMPLEMENT_SERVERCLASS_ST( CWeaponOldManHarpoon, DT_WeaponOldManHarpoon )
|
||||
END_SEND_TABLE()
|
||||
|
||||
BEGIN_DATADESC( CWeaponOldManHarpoon )
|
||||
END_DATADESC()
|
||||
|
||||
LINK_ENTITY_TO_CLASS( weapon_oldmanharpoon, CWeaponOldManHarpoon );
|
||||
PRECACHE_WEAPON_REGISTER( weapon_oldmanharpoon );
|
||||
|
||||
acttable_t CWeaponOldManHarpoon::m_acttable[] =
|
||||
{
|
||||
{ ACT_IDLE, ACT_IDLE_SUITCASE, false },
|
||||
{ ACT_WALK, ACT_WALK_SUITCASE, false },
|
||||
};
|
||||
IMPLEMENT_ACTTABLE( CWeaponOldManHarpoon );
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||||
//
|
||||
// Helper functions for the striderbuster weapon.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef WEAPON_STRIDERBUSTER_H
|
||||
#define WEAPON_STRIDERBUSTER_H
|
||||
#ifdef _WIN32
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
bool StriderBuster_IsAttachedStriderBuster( CBaseEntity *pEntity, CBaseEntity *pAttachedTo = NULL );
|
||||
void StriderBuster_OnAddToCargoHold( CBaseEntity *pEntity );
|
||||
bool StriderBuster_OnFlechetteAttach( CBaseEntity *pEntity, Vector &vecForceDir );
|
||||
int StriderBuster_NumFlechettesAttached( CBaseEntity *pEntity );
|
||||
float StriderBuster_GetPickupTime( CBaseEntity *pEntity );
|
||||
bool StriderBuster_WasKnockedOffStrider( CBaseEntity *pEntity );
|
||||
|
||||
#endif // WEAPON_STRIDERBUSTER_H
|
||||
Reference in New Issue
Block a user