This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
@@ -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
+74
View File
@@ -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;
}
+31
View File
@@ -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
+585
View File
@@ -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 );
+532
View File
@@ -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
+581
View File
@@ -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;
}
+46
View File
@@ -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
+25
View File
@@ -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
+127
View File
@@ -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
//-----------------------------------------------------------------------------
+121
View File
@@ -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 );
}
+150
View File
@@ -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
+508
View File
@@ -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