This commit is contained in:
nephacks
2025-06-04 03:22:50 +02:00
parent f234f23848
commit f12416cffd
14243 changed files with 6446499 additions and 26 deletions
+139
View File
@@ -0,0 +1,139 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Hooks and classes for the support of humanoid NPCs with
// groovy facial animation capabilities, aka, "Actors"
//
//=============================================================================//
#include "cbase.h"
#include "AI_Interest_Target.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
bool CAI_InterestTarget_t::IsThis( CBaseEntity *pThis )
{
return (pThis == m_hTarget);
};
const Vector &CAI_InterestTarget_t::GetPosition( void )
{
if (m_eType == LOOKAT_ENTITY && m_hTarget != NULL)
{
m_vecPosition = m_hTarget->EyePosition();
}
return m_vecPosition;
};
bool CAI_InterestTarget_t::IsActive( void )
{
if (m_flEndTime < gpGlobals->curtime) return false;
if (m_eType == LOOKAT_ENTITY && m_hTarget == NULL) return false;
return true;
};
float CAI_InterestTarget_t::Interest( void )
{
float t = (gpGlobals->curtime - m_flStartTime) / (m_flEndTime - m_flStartTime);
if (t < 0.0f || t > 1.0f)
return 0.0f;
if (m_flRamp && t < 1 - m_flRamp)
{
//t = t / m_flRamp;
t = 1.0 - ExponentialDecay( 0.2, m_flRamp, t );
//t = 1.0 - ExponentialDecay( 0.01, 1 - m_flRamp, t );
}
else if (t > 1.0f - m_flRamp)
{
t = (1.0 - t) / m_flRamp;
t = 3.0f * t * t - 2.0f * t * t * t;
}
else
{
t = 1.0f;
}
// ramp
t *= m_flInterest;
return t;
}
void CAI_InterestTarget::Add( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp )
{
int i;
for (i = 0; i < Count(); i++)
{
CAI_InterestTarget_t &target = Element( i );
if (target.m_hTarget == pTarget && target.m_flRamp == 0)
{
if (target.m_flStartTime == gpGlobals->curtime)
{
flImportance = MAX( flImportance, target.m_flInterest );
}
Remove( i );
break;
}
}
Add( CAI_InterestTarget_t::LOOKAT_ENTITY, pTarget, Vector( 0, 0, 0 ), flImportance, flDuration, flRamp );
}
void CAI_InterestTarget::Add( const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
{
int i;
for (i = 0; i < Count(); i++)
{
CAI_InterestTarget_t &target = Element( i );
if (target.m_vecPosition == vecPosition)
{
Remove( i );
break;
}
}
Add( CAI_InterestTarget_t::LOOKAT_POSITION, NULL, vecPosition, flImportance, flDuration, flRamp );
}
void CAI_InterestTarget::Add( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
{
int i;
for (i = 0; i < Count(); i++)
{
CAI_InterestTarget_t &target = Element( i );
if (target.m_hTarget == pTarget)
{
if (target.m_flStartTime == gpGlobals->curtime)
{
flImportance = MAX( flImportance, target.m_flInterest );
}
Remove( i );
break;
}
}
Add( CAI_InterestTarget_t::LOOKAT_BOTH, pTarget, vecPosition, flImportance, flDuration, flRamp );
}
void CAI_InterestTarget::Add( CAI_InterestTarget_t::CAI_InterestTarget_e type, CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp )
{
int i = AddToTail();
CAI_InterestTarget_t &target = Element( i );
target.m_eType = type;
target.m_hTarget = pTarget;
target.m_vecPosition = vecPosition;
target.m_flInterest = flImportance;
target.m_flStartTime = gpGlobals->curtime;
target.m_flEndTime = gpGlobals->curtime + flDuration;
target.m_flRamp = flRamp / flDuration;
}
+88
View File
@@ -0,0 +1,88 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Hooks and classes for the support of humanoid NPCs with
// groovy facial animation capabilities, aka, "Actors"
//
//=============================================================================//
#ifndef AI_INTEREST_TARGET_H
#define AI_INTEREST_TARGET_H
#if defined( _WIN32 )
#pragma once
#endif
//-----------------------------------------------------------------------------
// CAI_BaseActor
//
// Purpose: The base class for all facially expressive NPCS.
//
//-----------------------------------------------------------------------------
class CAI_InterestTarget_t
{
public:
enum CAI_InterestTarget_e
{
LOOKAT_ENTITY = 0,
LOOKAT_POSITION,
LOOKAT_BOTH
};
public:
bool IsThis( CBaseEntity *pThis );
const Vector &GetPosition( void );
bool IsActive( void );
float Interest( void );
public:
CAI_InterestTarget_e m_eType; // ????
EHANDLE m_hTarget;
Vector m_vecPosition;
float m_flStartTime;
float m_flEndTime;
float m_flRamp;
float m_flInterest;
DECLARE_SIMPLE_DATADESC();
};
class CAI_InterestTarget : public CUtlVector<CAI_InterestTarget_t>
{
public:
void Add( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp );
void Add( const Vector &vecPosition, float flImportance, float flDuration, float flRamp );
void Add( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp );
int Find( CBaseEntity *pTarget )
{
int i;
for ( i = 0; i < Count(); i++)
{
if (pTarget == (*this)[i].m_hTarget)
return i;
}
return InvalidIndex();
}
void Cleanup( void )
{
int i;
for (i = Count() - 1; i >= 0; i--)
{
if (!Element(i).IsActive())
{
Remove( i );
}
}
};
private:
void Add( CAI_InterestTarget_t::CAI_InterestTarget_e type, CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp );
};
//-----------------------------------------------------------------------------
#endif // AI_INTEREST_TARGET_H
File diff suppressed because it is too large Load Diff
+320
View File
@@ -0,0 +1,320 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// #include "BaseAnimating.h"
#ifndef BASE_ANIMATING_OVERLAY_H
#define BASE_ANIMATING_OVERLAY_H
#ifdef _WIN32
#pragma once
#endif
class CBaseAnimatingOverlay;
class IBoneSetup;
class CAnimationLayer : public CMemZeroOnNew
{
public:
DECLARE_CLASS_NOBASE( CAnimationLayer );
CAnimationLayer( void );
void Init( CBaseAnimatingOverlay *pOverlay );
// float SetBlending( int iBlender, float flValue, CBaseAnimating *pOwner );
void StudioFrameAdvance( float flInterval, CBaseAnimating *pOwner );
void DispatchAnimEvents( CBaseAnimating *eventHandler, CBaseAnimating *pOwner );
void SetOrder( int nOrder );
float GetFadeout( float flCurTime );
// For CNetworkVars.
void NetworkStateChanged();
void NetworkStateChanged( void *pVar );
public:
#define ANIM_LAYER_ACTIVE 0x0001
#define ANIM_LAYER_AUTOKILL 0x0002
#define ANIM_LAYER_KILLME 0x0004
#define ANIM_LAYER_DONTRESTORE 0x0008
#define ANIM_LAYER_CHECKACCESS 0x0010
#define ANIM_LAYER_DYING 0x0020
#define ANIM_LAYER_NOEVENTS 0x0040
int m_fFlags;
bool m_bSequenceFinished;
bool m_bLooping;
CNetworkVar( int, m_nSequence );
CNetworkVar( float, m_flCycle );
CNetworkVar( float, m_flPlaybackRate );
CNetworkVar( float, m_flPrevCycle );
CNetworkVar( float, m_flWeight );
CNetworkVar( float, m_flWeightDeltaRate );
float m_flBlendIn; // start and end blend frac (0.0 for now blend)
float m_flBlendOut;
float m_flKillRate;
float m_flKillDelay;
float m_flLayerAnimtime;
float m_flLayerFadeOuttime;
// dispatch flags
CStudioHdr *m_pDispatchedStudioHdr;
int m_nDispatchedSrc;
int m_nDispatchedDst;
// For checking for duplicates
Activity m_nActivity;
// order of layering on client
int m_nPriority;
CNetworkVar( int, m_nOrder );
int GetOrder( void ) { return m_nOrder; }
bool IsActive( void ) { return ((m_fFlags & ANIM_LAYER_ACTIVE) != 0); }
bool IsAutokill( void ) { return ((m_fFlags & ANIM_LAYER_AUTOKILL) != 0); }
bool IsKillMe( void ) { return ((m_fFlags & ANIM_LAYER_KILLME) != 0); }
bool IsAutoramp( void ) { return (m_flBlendIn != 0.0 || m_flBlendOut != 0.0); }
void KillMe( void ) { m_fFlags |= ANIM_LAYER_KILLME; }
void Dying( void ) { m_fFlags |= ANIM_LAYER_DYING; }
bool IsDying( void ) { return ((m_fFlags & ANIM_LAYER_DYING) != 0); }
void Dead( void ) { m_fFlags &= ~ANIM_LAYER_DYING; }
bool NoEvents( void ) { return ((m_fFlags & ANIM_LAYER_NOEVENTS) != 0); }
void SetSequence( int nSequence );
void SetCycle( float flCycle );
void SetPrevCycle( float flCycle );
void SetPlaybackRate( float flPlaybackRate );
void SetWeight( float flWeight );
void SetWeightDeltaRate( float flDelta );
int GetSequence( ) const;
float GetCycle( ) const;
float GetPrevCycle( ) const;
float GetPlaybackRate( ) const;
float GetWeight( ) const;
float GetWeightDeltaRate( ) const;
bool IsAbandoned( void );
void MarkActive( void );
float m_flLastEventCheck;
float m_flLastAccess;
// Network state changes get forwarded here.
CBaseAnimatingOverlay *m_pOwnerEntity;
DECLARE_SIMPLE_DATADESC();
};
inline float CAnimationLayer::GetFadeout( float flCurTime )
{
float s;
if (m_flLayerFadeOuttime <= 0.0f)
{
s = 0;
}
else
{
// blend in over 0.2 seconds
s = 1.0 - (flCurTime - m_flLayerAnimtime) / m_flLayerFadeOuttime;
if (s > 0 && s <= 1.0)
{
// do a nice spline curve
s = 3 * s * s - 2 * s * s * s;
}
else if ( s > 1.0f )
{
// Shouldn't happen, but maybe curtime is behind animtime?
s = 1.0f;
}
}
return s;
}
FORCEINLINE void CAnimationLayer::SetSequence( int nSequence )
{
m_nSequence = nSequence;
}
FORCEINLINE void CAnimationLayer::SetCycle( float flCycle )
{
m_flCycle = flCycle;
}
FORCEINLINE void CAnimationLayer::SetWeight( float flWeight )
{
m_flWeight = flWeight;
}
FORCEINLINE void CAnimationLayer::SetWeightDeltaRate( float flDelta )
{
m_flWeightDeltaRate = flDelta;
}
FORCEINLINE void CAnimationLayer::SetPrevCycle( float flPrevCycle )
{
m_flPrevCycle = flPrevCycle;
}
FORCEINLINE void CAnimationLayer::SetPlaybackRate( float flPlaybackRate )
{
m_flPlaybackRate = flPlaybackRate;
}
FORCEINLINE int CAnimationLayer::GetSequence( ) const
{
return m_nSequence;
}
FORCEINLINE float CAnimationLayer::GetCycle( ) const
{
return m_flCycle;
}
FORCEINLINE float CAnimationLayer::GetPrevCycle( ) const
{
return m_flPrevCycle;
}
FORCEINLINE float CAnimationLayer::GetPlaybackRate( ) const
{
return m_flPlaybackRate;
}
FORCEINLINE float CAnimationLayer::GetWeight( ) const
{
return m_flWeight;
}
FORCEINLINE float CAnimationLayer::GetWeightDeltaRate( ) const
{
return m_flWeightDeltaRate;
}
class CBaseAnimatingOverlay : public CBaseAnimating
{
DECLARE_CLASS( CBaseAnimatingOverlay, CBaseAnimating );
public:
enum
{
MAX_OVERLAYS = 15,
};
private:
CUtlVector< CAnimationLayer > m_AnimOverlay;
//int m_nActiveLayers;
//int m_nActiveBaseLayers;
public:
virtual CBaseAnimatingOverlay * GetBaseAnimatingOverlay() { return this; }
virtual void OnRestore();
virtual void SetModel( const char *szModelName );
virtual void StudioFrameAdvance();
virtual void DispatchAnimEvents ( CBaseAnimating *eventHandler );
virtual void GetSkeleton( CStudioHdr *pStudioHdr, BoneVector pos[], BoneQuaternionAligned q[], int boneMask );
int AddGestureSequence( int sequence, bool autokill = true );
int AddGestureSequence( int sequence, float flDuration, bool autokill = true );
int AddGesture( Activity activity, bool autokill = true );
int AddGesture( Activity activity, float flDuration, bool autokill = true );
bool IsPlayingGesture( Activity activity );
void RestartGesture( Activity activity, bool addifmissing = true, bool autokill = true );
void RemoveGesture( Activity activity );
void RemoveAllGestures( void );
int AddLayeredSequence( int sequence, int iPriority );
void SetLayerPriority( int iLayer, int iPriority );
bool IsValidLayer( int iLayer );
void SetLayerDuration( int iLayer, float flDuration );
float GetLayerDuration( int iLayer );
void SetLayerCycle( int iLayer, float flCycle );
void SetLayerCycle( int iLayer, float flCycle, float flPrevCycle );
float GetLayerCycle( int iLayer );
void SetLayerPlaybackRate( int iLayer, float flPlaybackRate );
void SetLayerWeight( int iLayer, float flWeight );
float GetLayerWeight( int iLayer );
void SetLayerBlendIn( int iLayer, float flBlendIn );
void SetLayerBlendOut( int iLayer, float flBlendOut );
void SetLayerAutokill( int iLayer, bool bAutokill );
void SetLayerLooping( int iLayer, bool bLooping );
void SetLayerNoRestore( int iLayer, bool bNoRestore );
void SetLayerNoEvents( int iLayer, bool bNoEvents );
Activity GetLayerActivity( int iLayer );
int GetLayerSequence( int iLayer );
int FindGestureLayer( Activity activity );
void RemoveLayer( int iLayer, float flKillRate = 0.2, float flKillDelay = 0.0 );
void FastRemoveLayer( int iLayer );
CAnimationLayer *GetAnimOverlay( int iIndex, bool bUseOrder = true );
int GetNumAnimOverlays() const;
void SetNumAnimOverlays( int num );
void VerifyOrder( void );
bool HasActiveLayer( void );
virtual bool UpdateDispatchLayer( CAnimationLayer *pLayer, CStudioHdr *pWeaponStudioHdr, int iSequence );
void AccumulateDispatchedLayers( CBaseAnimatingOverlay *pWeapon, CStudioHdr *pWeaponStudioHdr, IBoneSetup &boneSetup, BoneVector pos[], BoneQuaternion q[], float currentTime );
void RegenerateDispatchedLayers( IBoneSetup &boneSetup, BoneVector pos[], BoneQuaternion q[], float currentTime );
private:
int AllocateLayer( int iPriority = 0 ); // lower priorities are processed first
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
DECLARE_PREDICTABLE();
};
EXTERN_SEND_TABLE(DT_BaseAnimatingOverlay);
inline int CBaseAnimatingOverlay::GetNumAnimOverlays() const
{
return m_AnimOverlay.Count();
}
// ------------------------------------------------------------------------------------------ //
// CAnimationLayer inlines.
// ------------------------------------------------------------------------------------------ //
inline void CAnimationLayer::SetOrder( int nOrder )
{
m_nOrder = nOrder;
}
inline void CAnimationLayer::NetworkStateChanged()
{
if ( m_pOwnerEntity )
m_pOwnerEntity->NetworkStateChanged();
}
inline void CAnimationLayer::NetworkStateChanged( void *pVar )
{
if ( m_pOwnerEntity )
m_pOwnerEntity->NetworkStateChanged();
}
#endif // BASE_ANIMATING_OVERLAY_H
+484
View File
@@ -0,0 +1,484 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: A base class for model-based doors. The exact movement required to
// open or close the door is not dictated by this class, only that
// the door has open, closed, opening, and closing states.
//
// Doors must satisfy these requirements:
//
// - Derived classes must support being opened by NPCs.
// - Never autoclose in the face of a player.
// - Never close into an NPC.
//
//=============================================================================//
#ifndef BASEPROPDOOR_H
#define BASEPROPDOOR_H
#ifdef _WIN32
#pragma once
#endif
#include "props.h"
#include "locksounds.h"
#include "entityoutput.h"
#include "entityblocker.h"
extern ConVar g_debug_doors;
struct opendata_t
{
Vector vecStandPos; // Where the NPC should stand.
Vector vecFaceDir; // What direction the NPC should face.
Activity eActivity; // What activity the NPC should play.
};
abstract_class CBasePropDoor : public CDynamicProp
{
public:
DECLARE_CLASS( CBasePropDoor, CDynamicProp );
DECLARE_SERVERCLASS();
CBasePropDoor( void );
void Spawn();
void Precache();
void Activate();
int ObjectCaps();
virtual bool IsAbleToCloseAreaPortals( void ) const;
void HandleAnimEvent( animevent_t *pEvent );
// Base class services.
// Do not make the functions in this block virtual!!
// {
inline bool IsDoorOpen();
inline bool IsDoorAjar();
inline bool IsDoorOpening();
inline bool IsDoorClosed();
inline bool IsDoorClosing();
inline bool IsDoorBlocked() const;
inline bool IsNPCOpening(CAI_BaseNPC *pNPC);
inline bool IsPlayerOpening();
inline bool IsOpener(CBaseEntity *pEnt);
virtual bool IsDoorLocked() { return m_bLocked; }
bool NPCOpenDoor(CAI_BaseNPC *pNPC);
bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace );
// }
// Implement these in your leaf class.
// {
virtual bool DoorCanClose( bool bAutoClose ) { return true; }
virtual bool DoorCanOpen( void ) { return true; }
virtual void GetNPCOpenData(CAI_BaseNPC *pNPC, opendata_t &opendata) = 0;
virtual float GetOpenInterval(void) = 0;
enum DoorExtent_t
{
DOOR_EXTENT_OPEN = 1,
DOOR_EXTENT_CLOSED = 2,
};
virtual void ComputeDoorExtent( Extent *extent, unsigned int extentType ) = 0; // extent contains the volume encompassing by the door in the specified states
// }
protected:
enum DoorState_t
{
DOOR_STATE_CLOSED = 0,
DOOR_STATE_OPENING,
DOOR_STATE_OPEN,
DOOR_STATE_CLOSING,
DOOR_STATE_AJAR,
};
// dvs: FIXME: make these private
void DoorClose();
CBasePropDoor *GetMaster( void ) { return m_hMaster; }
bool HasSlaves( void ) { return ( m_hDoorList.Count() > 0 ); }
inline void SetDoorState( DoorState_t eDoorState );
virtual void CalcDoorSounds();
float m_flAutoReturnDelay; // How many seconds to wait before automatically closing, -1 never closes automatically.
CUtlVector< CHandle< CBasePropDoor > > m_hDoorList; // List of doors linked to us
inline CBaseEntity *GetActivator();
int m_nHardwareType;
// Called when the door becomes fully closed.
virtual void OnDoorClosed() {}
private:
// Implement these in your leaf class.
// {
// Called when the door becomes fully open.
virtual void OnDoorOpened() {}
// Called to tell the door to start opening.
virtual void BeginOpening(CBaseEntity *pOpenAwayFrom) = 0;
// Called to tell the door to start closing.
virtual void BeginClosing( void ) = 0;
// Called when blocked to tell the door to stop moving.
virtual void DoorStop( void ) = 0;
// Called when blocked to tell the door to continue moving.
virtual void DoorResume( void ) = 0;
// Called to send the door instantly to its spawn positions.
virtual void DoorTeleportToSpawnPosition() = 0;
// }
protected:
void UpdateAreaPortals( bool bOpen );
void DisableAreaPortalThink( void );
virtual void Lock();
virtual void Unlock();
private:
// Main entry points for the door base behaviors.
// Do not make the functions in this block virtual!!
// {
bool DoorActivate();
void DoorOpen( CBaseEntity *pOpenAwayFrom );
void OpenIfUnlocked(CBaseEntity *pActivator, CBaseEntity *pOpenAwayFrom);
void DoorOpenMoveDone();
void DoorCloseMoveDone();
void DoorAutoCloseThink();
void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value);
void OnUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
inline bool WillAutoReturn() { return m_flAutoReturnDelay != -1; }
void StartBlocked(CBaseEntity *pOther);
void OnStartBlocked( CBaseEntity *pOther );
void MasterStartBlocked( CBaseEntity *pOther );
void Blocked(CBaseEntity *pOther);
void EndBlocked(void);
void OnEndBlocked( void );
// Input handlers
void InputClose(inputdata_t &inputdata);
void InputLock(inputdata_t &inputdata);
void InputOpen(inputdata_t &inputdata);
void InputOpenAwayFrom(inputdata_t &inputdata);
void InputToggle(inputdata_t &inputdata);
void InputUnlock(inputdata_t &inputdata);
void SetDoorBlocker( CBaseEntity *pBlocker );
void SetMaster( CBasePropDoor *pMaster ) { m_hMaster = pMaster; }
DoorState_t m_eDoorState; // Holds whether the door is open, closed, opening, or closing.
locksound_t m_ls; // The sounds the door plays when being locked, unlocked, etc.
EHANDLE m_hActivator;
EHANDLE m_hBlocker; // Entity blocking the door currently
bool m_bFirstBlocked; // Marker for being the first door (in a group) to be blocked (needed for motion control)
protected:
bool m_bLocked; // True if the door is locked.
bool m_bForceClosed; // True if this door must close no matter what.
string_t m_SoundMoving;
string_t m_SoundOpen;
string_t m_SoundClose;
int m_nPhysicsMaterial;
// dvs: FIXME: can we remove m_flSpeed from CBaseEntity?
//float m_flSpeed; // Rotation speed when opening or closing in degrees per second.
DECLARE_DATADESC();
string_t m_SlaveName;
CHandle< CBasePropDoor > m_hMaster;
static void RegisterPrivateActivities();
// Outputs
COutputEvent m_OnBlockedClosing; // Triggered when the door becomes blocked while closing.
COutputEvent m_OnBlockedOpening; // Triggered when the door becomes blocked while opening.
COutputEvent m_OnUnblockedClosing; // Triggered when the door becomes unblocked while closing.
COutputEvent m_OnUnblockedOpening; // Triggered when the door becomes unblocked while opening.
COutputEvent m_OnFullyClosed; // Triggered when the door reaches the fully closed position.
COutputEvent m_OnFullyOpen; // Triggered when the door reaches the fully open position.
COutputEvent m_OnClose; // Triggered when the door is told to close.
COutputEvent m_OnOpen; // Triggered when the door is told to open.
COutputEvent m_OnLockedUse; // Triggered when the user tries to open a locked door.
};
void CBasePropDoor::SetDoorState( DoorState_t eDoorState )
{
m_eDoorState = eDoorState;
}
bool CBasePropDoor::IsDoorOpen()
{
return m_eDoorState == DOOR_STATE_OPEN;
}
bool CBasePropDoor::IsDoorAjar()
{
return ( m_eDoorState == DOOR_STATE_AJAR );
}
bool CBasePropDoor::IsDoorOpening()
{
return m_eDoorState == DOOR_STATE_OPENING;
}
bool CBasePropDoor::IsDoorClosed()
{
return m_eDoorState == DOOR_STATE_CLOSED;
}
bool CBasePropDoor::IsDoorClosing()
{
return m_eDoorState == DOOR_STATE_CLOSING;
}
CBaseEntity *CBasePropDoor::GetActivator()
{
return m_hActivator;
}
bool CBasePropDoor::IsDoorBlocked() const
{
return ( m_hBlocker != NULL );
}
bool CBasePropDoor::IsNPCOpening( CAI_BaseNPC *pNPC )
{
return ( pNPC == ( CAI_BaseNPC * )GetActivator() );
}
inline bool CBasePropDoor::IsPlayerOpening()
{
return ( GetActivator() && GetActivator()->IsPlayer() );
}
inline bool CBasePropDoor::IsOpener(CBaseEntity *pEnt)
{
return ( GetActivator() == pEnt );
}
//===============================================
// Rotating prop door
//===============================================
// Check directions for door movement
enum doorCheck_e
{
DOOR_CHECK_FORWARD, // Door's forward opening direction
DOOR_CHECK_BACKWARD, // Door's backward opening direction
DOOR_CHECK_FULL, // Door's complete movement volume
};
enum PropDoorRotatingSpawnPos_t
{
DOOR_SPAWN_CLOSED = 0,
DOOR_SPAWN_OPEN_FORWARD,
DOOR_SPAWN_OPEN_BACK,
DOOR_SPAWN_AJAR,
};
enum PropDoorRotatingOpenDirection_e
{
DOOR_ROTATING_OPEN_BOTH_WAYS = 0,
DOOR_ROTATING_OPEN_FORWARD,
DOOR_ROTATING_OPEN_BACKWARD,
};
class CPropDoorRotating : public CBasePropDoor
{
DECLARE_CLASS( CPropDoorRotating, CBasePropDoor );
public:
~CPropDoorRotating();
int DrawDebugTextOverlays( void );
void Spawn( void );
void MoveDone( void );
void BeginOpening( CBaseEntity *pOpenAwayFrom );
void BeginClosing( void );
void OnRestore( void );
void DoorTeleportToSpawnPosition();
void GetNPCOpenData( CAI_BaseNPC *pNPC, opendata_t &opendata );
void DoorClose( void );
bool DoorCanClose( bool bAutoClose );
void DoorOpen( CBaseEntity *pOpenAwayFrom );
void OnDoorOpened();
void OnDoorClosed();
void DoorResume( void );
void DoorStop( void );
float GetOpenInterval();
bool OverridePropdata() { return true; }
void InputSetSpeed( inputdata_t &inputdata );
virtual void ComputeDoorExtent( Extent *extent, unsigned int extentType ); // extent contains the volume encompassing open + closed states
virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_ALWAYS ); }
DECLARE_DATADESC();
DECLARE_SERVERCLASS();
private:
bool IsHingeOnLeft();
void AngularMove( const QAngle &vecDestAngle, float flSpeed );
void CalculateDoorVolume( QAngle closedAngles, QAngle openAngles, Vector *destMins, Vector *destMaxs );
bool CheckDoorClear( doorCheck_e state );
doorCheck_e GetOpenState( void );
void InputSetRotationDistance( inputdata_t &inputdata ); // Set the degree difference between open and closed
void InputMoveToRotationDistance( inputdata_t &inputdata ); // Set the degree difference between open and closed and move to open
void CalcOpenAngles( void ); // Subroutine to setup the m_angRotation QAngles based on the m_flDistance variable
Vector m_vecAxis; // The axis of rotation.
float m_flDistance; // How many degrees we rotate between open and closed.
PropDoorRotatingSpawnPos_t m_eSpawnPosition;
PropDoorRotatingOpenDirection_e m_eOpenDirection;
QAngle m_angRotationAjar; // Angles to spawn at if we are set to spawn ajar.
QAngle m_angRotationClosed; // Our angles when we are fully closed.
QAngle m_angRotationOpenForward; // Our angles when we are fully open towards our forward vector.
QAngle m_angRotationOpenBack; // Our angles when we are fully open away from our forward vector.
QAngle m_angGoal;
Vector m_vecForwardBoundsMin;
Vector m_vecForwardBoundsMax;
Vector m_vecBackBoundsMin;
Vector m_vecBackBoundsMax;
COutputEvent m_OnRotationDone; // Triggered when we finish rotating.
CHandle<CEntityBlocker> m_hDoorBlocker;
};
//--------------------------------------------------------------------------------------------------------
class CPropDoorRotatingBreakable : public CPropDoorRotating
{
DECLARE_CLASS( CPropDoorRotatingBreakable, CPropDoorRotating );
public:
DECLARE_DATADESC();
virtual void Spawn( void );
virtual void Precache( void );
virtual void UpdateOnRemove( void );
void PrecacheBreakables( void );
virtual int OnTakeDamage( const CTakeDamageInfo &info );
virtual void Event_Killed( const CTakeDamageInfo &info );
void InputSetRotationDistance( inputdata_t &inputdata );
void InputSetUnbreakable( inputdata_t &inputdata );
void InputSetBreakable( inputdata_t &inputdata );
virtual bool IsAbleToCloseAreaPortals( void ) const;
virtual int DrawDebugTextOverlays( void );
bool IsBreakable( void ) { return m_bBreakable; }
virtual void Lock();
virtual void Unlock();
virtual void OnDoorOpened( void )
{
UnblockNav();
BaseClass::OnDoorOpened();
}
virtual void OnDoorClosed( void )
{
BaseClass::OnDoorClosed();
}
bool operator()( CNavArea *area ); // functor that blocks areas in our extent
static bool CalculateBlocked( bool *pResultByTeam, const Vector &vecMins, const Vector &vecMaxs );
private:
void UpdateBlocked( bool bBlocked );
void BlockNav( void );
void UnblockNav( void );
// void BlockNavArea( bool blocked )
// {
// /**
// * MSB: I'm commenting this out, because we can't use BLOCKED for this,
// * since *nothing* can path thru a blocked area - SurvivorBots, population
// * algorithms, etc.
// * However, something like "closed door" might be useful to flag here
// * in the future.
// *
//
// if ( blocked )
// {
// CNavArea *area = TheNavMesh->GetNavArea( WorldSpaceCenter() );
// if ( area )
// {
// area->Block();
// m_blockedNavAreaID = area->GetID();
// }
// }
// else
// {
// if ( m_blockedNavAreaID > 0 )
// {
// CNavArea *area = TheNavMesh->GetNavAreaByID( m_blockedNavAreaID );
// if ( area )
// {
// area->UpdateBlocked( true ); // give it a chance to stay blocked by something else
// }
// }
// }
// */
// }
int m_blockedNavAreaID;
bool m_bBreakable;
bool m_isAbleToCloseAreaPortals;
int m_currentDamageState;
int m_blockedTeamNumber;
bool m_isBlockingNav[MAX_NAV_TEAMS];
CUtlVector< string_t > m_damageStates;
};
#endif // BASEPROPDOOR_H
+222
View File
@@ -0,0 +1,222 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "game.h"
#include "CRagdollMagnet.h"
#include "cplane.h"
ConVar ai_debug_ragdoll_magnets( "ai_debug_ragdoll_magnets", "0");
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_ENTITY_TO_CLASS( phys_ragdollmagnet, CRagdollMagnet );
BEGIN_DATADESC( CRagdollMagnet )
DEFINE_KEYFIELD( m_radius, FIELD_FLOAT, "radius" ),
DEFINE_KEYFIELD( m_force, FIELD_FLOAT, "force" ),
DEFINE_KEYFIELD( m_axis, FIELD_VECTOR, "axis" ),
DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CRagdollMagnet::InputEnable( inputdata_t &inputdata )
{
Enable( true );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CRagdollMagnet::InputDisable( inputdata_t &inputdata )
{
Enable( false );
}
//-----------------------------------------------------------------------------
// Purpose: Find the ragdoll magnet entity that should pull this entity's ragdoll
// Input : *pNPC - the npc that's dying
// Output : CRagdollMagnet - the magnet that's best to use.
//
// NOTES:
//
// The nearest ragdoll magnet pulls me in IF:
// - Present
// - I'm within the magnet's RADIUS
// - LATER: There is clear line of sight from my center to the magnet
// - LATER: I'm not flagged to ignore ragdoll magnets
// - LATER: The magnet is not turned OFF
//-----------------------------------------------------------------------------
CRagdollMagnet *CRagdollMagnet::FindBestMagnet( CBaseEntity *pNPC )
{
CRagdollMagnet *pMagnet = NULL;
CRagdollMagnet *pBestMagnet;
float flClosestDist;
// Assume we won't find one.
pBestMagnet = NULL;
flClosestDist = FLT_MAX;
do
{
pMagnet = (CRagdollMagnet *)gEntList.FindEntityByClassname( pMagnet, "phys_ragdollmagnet" );
if( pMagnet && pMagnet->IsEnabled() )
{
if( pMagnet->m_target != NULL_STRING )
{
// if this magnet has a target, only affect that target!
if( pNPC->GetEntityName() == pMagnet->m_target )
{
return pMagnet;
}
else
{
continue;
}
}
float flDist;
flDist = pMagnet->DistToPoint( pNPC->WorldSpaceCenter() );
if( flDist < flClosestDist && flDist <= pMagnet->GetRadius() )
{
// This is the closest magnet that can pull this npc.
flClosestDist = flDist;
pBestMagnet = pMagnet;
}
}
} while( pMagnet );
return pBestMagnet;
}
//-----------------------------------------------------------------------------
// Purpose: Get the force that we should add to this NPC's ragdoll.
// Input : *pNPC -
// Output : Vector
//
// NOTE: This function assumes pNPC is within this magnet's radius.
//-----------------------------------------------------------------------------
Vector CRagdollMagnet::GetForceVector( CBaseEntity *pNPC )
{
Vector vecForceToApply;
if( IsBarMagnet() )
{
CPlane axis;
Vector vecForceDir;
Vector vecClosest;
CalcClosestPointOnLineSegment( pNPC->WorldSpaceCenter(), GetAbsOrigin(), m_axis, vecClosest, NULL );
vecForceDir = (vecClosest - pNPC->WorldSpaceCenter() );
VectorNormalize( vecForceDir );
vecForceToApply = vecForceDir * m_force;
}
else
{
Vector vecForce;
vecForce = GetAbsOrigin() - pNPC->WorldSpaceCenter();
VectorNormalize( vecForce );
vecForceToApply = vecForce * m_force;
}
if( ai_debug_ragdoll_magnets.GetBool() )
{
IPhysicsObject *pPhysObject;
pPhysObject = pNPC->VPhysicsGetObject();
if( pPhysObject )
{
Msg("Ragdoll magnet adding %f inches/sec to %s\n", m_force/pPhysObject->GetMass(), pNPC->GetClassname() );
}
}
return vecForceToApply;
}
//-----------------------------------------------------------------------------
// Purpose: How far away is this point? This is different for point and bar magnets
// Input : &vecPoint - the point
// Output : float - the dist
//-----------------------------------------------------------------------------
float CRagdollMagnet::DistToPoint( const Vector &vecPoint )
{
if( IsBarMagnet() )
{
// I'm a bar magnet, so the point's distance is really the plane constant.
// A bar magnet is a cylinder who's length is AbsOrigin() to m_axis, and whose
// diameter is m_radius.
// first we build two planes. The TOP and BOTTOM planes.
// the idea is that vecPoint must be on the right side of both
// planes to be affected by this particular magnet.
// TOP and BOTTOM planes can be visualized as the 'caps' of the cylinder
// that describes the bar magnet, and they point towards each other.
// We're making sure vecPoint is between the caps.
Vector vecAxis;
vecAxis = GetAxisVector();
VectorNormalize( vecAxis );
CPlane top, bottom;
bottom.InitializePlane( -vecAxis, m_axis );
top.InitializePlane( vecAxis, GetAbsOrigin() );
if( top.PointInFront( vecPoint ) && bottom.PointInFront( vecPoint ) )
{
// This point is between the two caps, so calculate the distance
// of vecPoint from the axis of the bar magnet
CPlane axis;
Vector vecUp;
Vector vecRight;
// Horizontal and Vertical distances.
float hDist, vDist;
// Need to get a vector that's right-hand to m_axis
VectorVectors( vecAxis, vecRight, vecUp );
//CrossProduct( vecAxis, vecUp, vecRight );
//VectorNormalize( vecRight );
//VectorNormalize( vecUp );
// Set up the plane to measure horizontal dist.
axis.InitializePlane( vecRight, GetAbsOrigin() );
hDist = fabs( axis.PointDist( vecPoint ) );
axis.InitializePlane( vecUp, GetAbsOrigin() );
vDist = fabs( axis.PointDist( vecPoint ) );
return MAX( hDist, vDist );
}
else
{
return FLT_MAX;
}
}
else
{
// I'm a point magnet. Just return dist
return ( GetAbsOrigin() - vecPoint ).Length();
}
}
+46
View File
@@ -0,0 +1,46 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Used to influence the initial force for a dying NPC's ragdoll.
// Passive entity. Just represents position in the world, radius, force
//
// $NoKeywords: $
//=============================================================================//
#pragma once
#ifndef CRAGDOLLMAGNET_H
#define CRAGDOLLMAGNET_H
#define SF_RAGDOLLMAGNET_BAR 0x00000002 // this is a bar magnet.
class CRagdollMagnet : public CPointEntity
{
public:
DECLARE_CLASS( CRagdollMagnet, CPointEntity );
DECLARE_DATADESC();
Vector GetForceVector( CBaseEntity *pNPC );
float GetRadius( void ) { return m_radius; }
Vector GetAxisVector( void ) { return m_axis - GetAbsOrigin(); }
float DistToPoint( const Vector &vecPoint );
bool IsEnabled( void ) { return !m_bDisabled; }
int IsBarMagnet( void ) { return (m_spawnflags & SF_RAGDOLLMAGNET_BAR); }
static CRagdollMagnet *FindBestMagnet( CBaseEntity *pNPC );
void Enable( bool bEnable ) { m_bDisabled = !bEnable; }
// Inputs
void InputEnable( inputdata_t &inputdata );
void InputDisable( inputdata_t &inputdata );
private:
bool m_bDisabled;
float m_radius;
float m_force;
Vector m_axis;
};
#endif //CRAGDOLLMAGNET_H
File diff suppressed because it is too large Load Diff
+198
View File
@@ -0,0 +1,198 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose: Utility code.
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "te.h"
#include "shake.h"
#include "decals.h"
#include "IEffects.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern int g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud
extern int g_sModelIndexBloodDrop; // (in combatweapon.cpp) holds the sprite index for the initial blood
extern int g_sModelIndexBloodSpray; // (in combatweapon.cpp) holds the sprite index for splattered blood
//-----------------------------------------------------------------------------
// Client-server neutral effects interface
//-----------------------------------------------------------------------------
class CEffectsServer : public IEffects
{
public:
CEffectsServer();
virtual ~CEffectsServer();
// Members of the IEffect interface
virtual void Beam( const Vector &Start, const Vector &End, int nModelIndex,
int nHaloIndex, unsigned char frameStart, unsigned char frameRate,
float flLife, unsigned char width, unsigned char endWidth, unsigned char fadeLength,
unsigned char noise, unsigned char red, unsigned char green,
unsigned char blue, unsigned char brightness, unsigned char speed);
virtual void Smoke( const Vector &origin, int mModel, float flScale, float flFramerate );
virtual void Sparks( const Vector &position, int nMagnitude = 1, int nTrailLength = 1, const Vector *pvecDir = NULL );
virtual void Dust( const Vector &pos, const Vector &dir, float size, float speed );
virtual void MuzzleFlash( const Vector &origin, const QAngle &angles, float scale, int type );
virtual void MetalSparks( const Vector &position, const Vector &direction );
virtual void EnergySplash( const Vector &position, const Vector &direction, bool bExplosive = false );
virtual void Ricochet( const Vector &position, const Vector &direction );
// FIXME: Should these methods remain in this interface? Or go in some
// other client-server neutral interface?
virtual float Time();
virtual bool IsServer();
virtual void SuppressEffectsSounds( bool bSuppress ) { Assert(0); }
private:
//-----------------------------------------------------------------------------
// Purpose: Returning true means don't even call TE func
// Input : filter -
// *suppress_host -
// Output : static bool
//-----------------------------------------------------------------------------
bool SuppressTE( CRecipientFilter& filter )
{
if ( GetSuppressHost() )
{
if ( !filter.IgnorePredictionCull() )
{
filter.RemoveRecipient( (CBasePlayer *)GetSuppressHost() );
}
if ( !filter.GetRecipientCount() )
{
// Suppress it
return true;
}
}
// There's at least one recipient
return false;
}
};
//-----------------------------------------------------------------------------
// Client-server neutral effects interface accessor
//-----------------------------------------------------------------------------
static CEffectsServer s_EffectServer;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CEffectsServer, IEffects, IEFFECTS_INTERFACE_VERSION, s_EffectServer);
IEffects *g_pEffects = &s_EffectServer;
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CEffectsServer::CEffectsServer()
{
}
CEffectsServer::~CEffectsServer()
{
}
//-----------------------------------------------------------------------------
// Generates a beam
//-----------------------------------------------------------------------------
void CEffectsServer::Beam( const Vector &vecStart, const Vector &vecEnd, int nModelIndex,
int nHaloIndex, unsigned char frameStart, unsigned char frameRate,
float flLife, unsigned char width, unsigned char endWidth, unsigned char fadeLength,
unsigned char noise, unsigned char red, unsigned char green,
unsigned char blue, unsigned char brightness, unsigned char speed)
{
CBroadcastRecipientFilter filter;
if ( !SuppressTE( filter ) )
{
te->BeamPoints( filter, 0.0,
&vecStart, &vecEnd, nModelIndex, nHaloIndex, frameStart, frameRate, flLife,
width, endWidth, fadeLength, noise, red, green, blue, brightness, speed );
}
}
//-----------------------------------------------------------------------------
// Generates various tempent effects
//-----------------------------------------------------------------------------
void CEffectsServer::Smoke( const Vector &origin, int mModel, float flScale, float flFramerate )
{
CPVSFilter filter( origin );
if ( !SuppressTE( filter ) )
{
te->Smoke( filter, 0.0, &origin, mModel, flScale * 0.1f, flFramerate );
}
}
void CEffectsServer::Sparks( const Vector &position, int nMagnitude, int nTrailLength, const Vector *pvecDir )
{
CPVSFilter filter( position );
if ( !SuppressTE( filter ) )
{
te->Sparks( filter, 0.0, &position, nMagnitude, nTrailLength, pvecDir );
}
}
void CEffectsServer::Dust( const Vector &pos, const Vector &dir, float size, float speed )
{
CPVSFilter filter( pos );
if ( !SuppressTE( filter ) )
{
te->Dust( filter, 0.0, pos, dir, size, speed );
}
}
void CEffectsServer::MuzzleFlash( const Vector &origin, const QAngle &angles, float scale, int type )
{
CPVSFilter filter( origin );
if ( !SuppressTE( filter ) )
{
te->MuzzleFlash( filter, 0.0f, origin, angles, scale, type );
}
}
void CEffectsServer::MetalSparks( const Vector &position, const Vector &direction )
{
CPVSFilter filter( position );
if ( !SuppressTE( filter ) )
{
te->MetalSparks( filter, 0.0, &position, &direction );
}
}
void CEffectsServer::EnergySplash( const Vector &position, const Vector &direction, bool bExplosive )
{
CPVSFilter filter( position );
if ( !SuppressTE( filter ) )
{
te->EnergySplash( filter, 0.0, &position, &direction, bExplosive );
}
}
void CEffectsServer::Ricochet( const Vector &position, const Vector &direction )
{
CPVSFilter filter( position );
if ( !SuppressTE( filter ) )
{
te->ArmorRicochet( filter, 0.0, &position, &direction );
}
}
//-----------------------------------------------------------------------------
// FIXME: Should these methods remain in this interface? Or go in some
// other client-server neutral interface?
//-----------------------------------------------------------------------------
float CEffectsServer::Time()
{
return gpGlobals->curtime;
}
bool CEffectsServer::IsServer()
{
return true;
}
+389
View File
@@ -0,0 +1,389 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Dissolve entity to be attached to target entity. Serves two purposes:
//
// 1) An entity that can be placed by a level designer and triggered
// to ignite a target entity.
//
// 2) An entity that can be created at runtime to ignite a target entity.
//
//=============================================================================//
#include "cbase.h"
#include "EntityDissolve.h"
#include "baseanimating.h"
#include "physics_prop_ragdoll.h"
#include "ai_basenpc.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static const char *s_pElectroThinkContext = "ElectroThinkContext";
//-----------------------------------------------------------------------------
// Lifetime
//-----------------------------------------------------------------------------
#define DISSOLVE_FADE_IN_START_TIME 0.0f
#define DISSOLVE_FADE_IN_END_TIME 1.0f
#define DISSOLVE_FADE_OUT_MODEL_START_TIME 1.9f
#define DISSOLVE_FADE_OUT_MODEL_END_TIME 2.0f
#define DISSOLVE_FADE_OUT_START_TIME 2.0f
#define DISSOLVE_FADE_OUT_END_TIME 2.0f
//-----------------------------------------------------------------------------
// Model
//-----------------------------------------------------------------------------
#define DISSOLVE_SPRITE_NAME "sprites/blueglow1.vmt"
//-----------------------------------------------------------------------------
// Save/load
//-----------------------------------------------------------------------------
BEGIN_DATADESC( CEntityDissolve )
DEFINE_FIELD( m_flStartTime, FIELD_TIME ),
DEFINE_FIELD( m_flFadeInStart, FIELD_FLOAT ),
DEFINE_FIELD( m_flFadeInLength, FIELD_FLOAT ),
DEFINE_FIELD( m_flFadeOutModelStart, FIELD_FLOAT ),
DEFINE_FIELD( m_flFadeOutModelLength, FIELD_FLOAT ),
DEFINE_FIELD( m_flFadeOutStart, FIELD_FLOAT ),
DEFINE_FIELD( m_flFadeOutLength, FIELD_FLOAT ),
DEFINE_KEYFIELD( m_nDissolveType, FIELD_INTEGER, "dissolvetype" ),
DEFINE_FIELD( m_vDissolverOrigin, FIELD_VECTOR ),
DEFINE_KEYFIELD( m_nMagnitude, FIELD_INTEGER, "magnitude" ),
DEFINE_FUNCTION( DissolveThink ),
DEFINE_FUNCTION( ElectrocuteThink ),
DEFINE_INPUTFUNC( FIELD_STRING, "Dissolve", InputDissolve ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Networking
//-----------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( CEntityDissolve, DT_EntityDissolve )
SendPropTime( SENDINFO( m_flStartTime ) ),
SendPropFloat( SENDINFO( m_flFadeInStart ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_flFadeInLength ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_flFadeOutModelStart ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_flFadeOutModelLength ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_flFadeOutStart ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_flFadeOutLength ), 0, SPROP_NOSCALE ),
SendPropInt( SENDINFO( m_nDissolveType ), ENTITY_DISSOLVE_BITS, SPROP_UNSIGNED ),
SendPropVector (SENDINFO(m_vDissolverOrigin), 0, SPROP_NOSCALE ),
SendPropInt( SENDINFO( m_nMagnitude ), 8, SPROP_UNSIGNED ),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( env_entity_dissolver, CEntityDissolve );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CEntityDissolve::CEntityDissolve( void )
{
m_flStartTime = 0.0f;
m_nMagnitude = 250;
}
CEntityDissolve::~CEntityDissolve( void )
{
}
//-----------------------------------------------------------------------------
// Precache
//-----------------------------------------------------------------------------
void CEntityDissolve::Precache()
{
if ( NULL_STRING == GetModelName() )
{
PrecacheModel( DISSOLVE_SPRITE_NAME );
}
else
{
PrecacheModel( STRING( GetModelName() ) );
}
}
//-----------------------------------------------------------------------------
// Spawn
//-----------------------------------------------------------------------------
void CEntityDissolve::Spawn()
{
BaseClass::Spawn();
Precache();
UTIL_SetModel( this, STRING( GetModelName() ) );
if ( (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (m_nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT) )
{
if ( dynamic_cast< CRagdollProp* >( GetMoveParent() ) )
{
SetContextThink( &CEntityDissolve::ElectrocuteThink, gpGlobals->curtime + 0.01f, s_pElectroThinkContext );
}
}
// Setup our times
m_flFadeInStart = DISSOLVE_FADE_IN_START_TIME;
m_flFadeInLength = DISSOLVE_FADE_IN_END_TIME - DISSOLVE_FADE_IN_START_TIME;
m_flFadeOutModelStart = DISSOLVE_FADE_OUT_MODEL_START_TIME;
m_flFadeOutModelLength = DISSOLVE_FADE_OUT_MODEL_END_TIME - DISSOLVE_FADE_OUT_MODEL_START_TIME;
m_flFadeOutStart = DISSOLVE_FADE_OUT_START_TIME;
m_flFadeOutLength = DISSOLVE_FADE_OUT_END_TIME - DISSOLVE_FADE_OUT_START_TIME;
if ( m_nDissolveType == ENTITY_DISSOLVE_CORE )
{
m_flFadeInStart = 0.0f;
m_flFadeOutStart = CORE_DISSOLVE_FADE_START;
m_flFadeOutModelStart = CORE_DISSOLVE_MODEL_FADE_START;
m_flFadeOutModelLength = CORE_DISSOLVE_MODEL_FADE_LENGTH;
m_flFadeInLength = CORE_DISSOLVE_FADEIN_LENGTH;
}
m_nRenderMode = kRenderTransColor;
SetRenderColor( 255, 255, 255 );
SetRenderAlpha( 255 );
m_nRenderFX = kRenderFxNone;
SetThink( &CEntityDissolve::DissolveThink );
if ( gpGlobals->curtime > m_flStartTime )
{
// Necessary for server-side ragdolls
DissolveThink();
}
else
{
SetNextThink( gpGlobals->curtime + 0.01f );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : inputdata -
//-----------------------------------------------------------------------------
void CEntityDissolve::InputDissolve( inputdata_t &inputdata )
{
string_t strTarget = inputdata.value.StringID();
if (strTarget == NULL_STRING)
{
strTarget = m_target;
}
CBaseEntity *pTarget = NULL;
while ((pTarget = gEntList.FindEntityGeneric(pTarget, STRING(strTarget), this, inputdata.pActivator)) != NULL)
{
CBaseAnimating *pBaseAnim = pTarget->GetBaseAnimating();
if (pBaseAnim)
{
pBaseAnim->Dissolve( NULL, gpGlobals->curtime, false, m_nDissolveType, GetAbsOrigin(), m_nMagnitude );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Creates a flame and attaches it to a target entity.
// Input : pTarget -
//-----------------------------------------------------------------------------
CEntityDissolve *CEntityDissolve::Create( CBaseEntity *pTarget, const char *pMaterialName,
float flStartTime, int nDissolveType, bool *pRagdollCreated )
{
if ( pRagdollCreated )
{
*pRagdollCreated = false;
}
if ( !pMaterialName )
{
pMaterialName = DISSOLVE_SPRITE_NAME;
}
if ( pTarget->IsPlayer() )
{
// Simply immediately kill the player.
CBasePlayer *pPlayer = assert_cast< CBasePlayer* >( pTarget );
pPlayer->SetArmorValue( 0 );
CTakeDamageInfo info( pPlayer, pPlayer, pPlayer->GetHealth(), DMG_GENERIC | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE );
pPlayer->TakeDamage( info );
return NULL;
}
CEntityDissolve *pDissolve = (CEntityDissolve *) CreateEntityByName( "env_entity_dissolver" );
if ( pDissolve == NULL )
return NULL;
pDissolve->m_nDissolveType = nDissolveType;
if ( (nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT) )
{
if ( pTarget->IsNPC() && pTarget->MyNPCPointer()->CanBecomeRagdoll() )
{
CTakeDamageInfo info;
CBaseEntity *pRagdoll = CreateServerRagdoll( pTarget->MyNPCPointer(), 0, info, COLLISION_GROUP_DEBRIS, true );
pRagdoll->SetCollisionBounds( pTarget->CollisionProp()->OBBMins(), pTarget->CollisionProp()->OBBMaxs() );
// Necessary to cause it to do the appropriate death cleanup
if ( pTarget->m_lifeState == LIFE_ALIVE )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
CTakeDamageInfo ragdollInfo( pPlayer, pPlayer, 10000.0, DMG_SHOCK | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE );
pTarget->TakeDamage( ragdollInfo );
}
if ( pRagdollCreated )
{
*pRagdollCreated = true;
}
UTIL_Remove( pTarget );
pTarget = pRagdoll;
}
}
pDissolve->SetModelName( AllocPooledString(pMaterialName) );
pDissolve->AttachToEntity( pTarget );
pDissolve->SetStartTime( flStartTime );
pDissolve->Spawn();
// Send to the client even though we don't have a model
pDissolve->AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
// Play any appropriate noises when we start to dissolve
if ( (nDissolveType == ENTITY_DISSOLVE_ELECTRICAL) || (nDissolveType == ENTITY_DISSOLVE_ELECTRICAL_LIGHT) )
{
pTarget->DispatchResponse( "TLK_ELECTROCUTESCREAM" );
}
else
{
pTarget->DispatchResponse( "TLK_DISSOLVESCREAM" );
}
return pDissolve;
}
//-----------------------------------------------------------------------------
// What type of dissolve?
//-----------------------------------------------------------------------------
CEntityDissolve *CEntityDissolve::Create( CBaseEntity *pTarget, CBaseEntity *pSource )
{
// Look for other boogies on the ragdoll + kill them
for ( CBaseEntity *pChild = pSource->FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() )
{
CEntityDissolve *pDissolve = dynamic_cast<CEntityDissolve*>(pChild);
if ( !pDissolve )
continue;
return Create( pTarget, STRING( pDissolve->GetModelName() ), pDissolve->m_flStartTime, pDissolve->m_nDissolveType );
}
return NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Attaches the flame to an entity and moves with it
// Input : pTarget - target entity to attach to
//-----------------------------------------------------------------------------
void CEntityDissolve::AttachToEntity( CBaseEntity *pTarget )
{
// So our dissolver follows the entity around on the server.
SetParent( pTarget );
SetLocalOrigin( vec3_origin );
SetLocalAngles( vec3_angle );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : lifetime -
//-----------------------------------------------------------------------------
void CEntityDissolve::SetStartTime( float flStartTime )
{
m_flStartTime = flStartTime;
}
//-----------------------------------------------------------------------------
// Purpose: Burn targets around us
//-----------------------------------------------------------------------------
void CEntityDissolve::DissolveThink( void )
{
CBaseAnimating *pTarget = ( GetMoveParent() ) ? GetMoveParent()->GetBaseAnimating() : NULL;
if ( GetModelName() == NULL_STRING && pTarget == NULL )
return;
if ( pTarget == NULL )
{
UTIL_Remove( this );
return;
}
// Turn them into debris
pTarget->SetCollisionGroup( COLLISION_GROUP_DISSOLVING );
if ( pTarget && pTarget->GetFlags() & FL_TRANSRAGDOLL )
{
SetRenderAlpha( 0 );
}
float dt = gpGlobals->curtime - m_flStartTime;
if ( dt < m_flFadeInStart )
{
SetNextThink( m_flStartTime + m_flFadeInStart );
return;
}
// If we're done fading, then kill our target entity and us
if ( dt >= m_flFadeOutStart + m_flFadeOutLength )
{
// Necessary to cause it to do the appropriate death cleanup
// Yeah, the player may have nothing to do with it, but
// passing NULL to TakeDamage causes bad things to happen
CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
int iNoPhysicsDamage = g_pGameRules->Damage_GetNoPhysicsForce();
CTakeDamageInfo info( pPlayer, pPlayer, 10000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL | iNoPhysicsDamage );
pTarget->TakeDamage( info );
if ( pTarget != pPlayer )
{
UTIL_Remove( pTarget );
}
UTIL_Remove( this );
return;
}
SetNextThink( gpGlobals->curtime + TICK_INTERVAL );
}
//-----------------------------------------------------------------------------
// Purpose: Burn targets around us
//-----------------------------------------------------------------------------
void CEntityDissolve::ElectrocuteThink( void )
{
CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( GetMoveParent() );
if ( !pRagdoll )
return;
ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( );
for ( int j = 0; j < pRagdollPhys->listCount; ++j )
{
Vector vecForce;
vecForce = RandomVector( -2400.0f, 2400.0f );
pRagdollPhys->list[j].pObject->ApplyForceCenter( vecForce );
}
SetContextThink( &CEntityDissolve::ElectrocuteThink, gpGlobals->curtime + random->RandomFloat( 0.1, 0.2f ),
s_pElectroThinkContext );
}
+63
View File
@@ -0,0 +1,63 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef ENTITYDISSOLVE_H
#define ENTITYDISSOLVE_H
#ifdef _WIN32
#pragma once
#endif
class CEntityDissolve : public CBaseEntity
{
public:
DECLARE_SERVERCLASS();
DECLARE_CLASS( CEntityDissolve, CBaseEntity );
CEntityDissolve( void );
~CEntityDissolve( void );
static CEntityDissolve *Create( CBaseEntity *pTarget, const char *pMaterialName,
float flStartTime, int nDissolveType = 0, bool *pRagdollCreated = NULL );
static CEntityDissolve *Create( CBaseEntity *pTarget, CBaseEntity *pSource );
void Precache();
void Spawn();
void AttachToEntity( CBaseEntity *pTarget );
void SetStartTime( float flStartTime );
void SetDissolverOrigin( Vector vOrigin ) { m_vDissolverOrigin = vOrigin; }
void SetMagnitude( int iMagnitude ){ m_nMagnitude = iMagnitude; }
void SetDissolveType( int iType ) { m_nDissolveType = iType; }
Vector GetDissolverOrigin( void )
{
Vector vReturn = m_vDissolverOrigin;
return vReturn;
}
int GetMagnitude( void ) { return m_nMagnitude; }
int GetDissolveType( void ) { return m_nDissolveType; }
DECLARE_DATADESC();
CNetworkVar( float, m_flStartTime );
CNetworkVar( float, m_flFadeInStart );
CNetworkVar( float, m_flFadeInLength );
CNetworkVar( float, m_flFadeOutModelStart );
CNetworkVar( float, m_flFadeOutModelLength );
CNetworkVar( float, m_flFadeOutStart );
CNetworkVar( float, m_flFadeOutLength );
protected:
void InputDissolve( inputdata_t &inputdata );
void DissolveThink( void );
void ElectrocuteThink( void );
CNetworkVar( int, m_nDissolveType );
CNetworkVector( m_vDissolverOrigin );
CNetworkVar( int, m_nMagnitude );
};
#endif // ENTITYDISSOLVE_H
+418
View File
@@ -0,0 +1,418 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose: Flame entity to be attached to target entity. Serves two purposes:
//
// 1) An entity that can be placed by a level designer and triggered
// to ignite a target entity.
//
// 2) An entity that can be created at runtime to ignite a target entity.
//
//===========================================================================//
#include "cbase.h"
#include "EntityFlame.h"
#include "ai_basenpc.h"
#ifdef INFESTED_DLL
#include "asw_fire.h"
#else
#include "fire.h"
#endif
#include "shareddefs.h"
#include "ai_link.h"
#include "ai_node.h"
#include "ai_network.h"
#include "ai_localnavigator.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
BEGIN_DATADESC( CEntityFlame )
DEFINE_FIELD( m_flLifetime, FIELD_TIME ),
DEFINE_FIELD( m_flSize, FIELD_FLOAT ),
DEFINE_FIELD( m_hEntAttached, FIELD_EHANDLE ),
DEFINE_FIELD( m_iDangerSound, FIELD_INTEGER ),
DEFINE_FIELD( m_bCheapEffect, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_bPlayingSound, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_DangerLinks, CUtlVector< CAI_Link* > ),
DEFINE_FUNCTION( FlameThink ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CEntityFlame, DT_EntityFlame )
SendPropEHandle( SENDINFO( m_hEntAttached ) ),
SendPropBool( SENDINFO( m_bCheapEffect ) ),
END_SEND_TABLE()
#ifndef INFESTED_DLL
LINK_ENTITY_TO_CLASS( entityflame, CEntityFlame );
#endif
PRECACHE_REGISTER(entityflame);
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CEntityFlame::CEntityFlame( void )
{
m_flSize = 0.0f;
m_flLifetime = gpGlobals->curtime;
m_bPlayingSound = false;
m_iDangerSound = SOUNDLIST_EMPTY;
m_bCheapEffect = false;
m_hObstacle = OBSTACLE_INVALID;
}
void CEntityFlame::UpdateOnRemove()
{
// Sometimes the entity I'm burning gets destroyed by other means,
// which kills me. Make sure to stop the burning sound.
if ( m_bPlayingSound )
{
EmitSound( "General.StopBurning" );
m_bPlayingSound = false;
}
if ( m_iDangerSound != SOUNDLIST_EMPTY )
{
CSoundEnt::FreeSound( m_iDangerSound );
m_iDangerSound = SOUNDLIST_EMPTY;
}
int nCount = m_DangerLinks.Count();
for ( int i = 0; i < nCount; ++i )
{
CAI_Link *pLink = m_DangerLinks[i];
--pLink->m_nDangerCount;
}
m_DangerLinks.RemoveAll();
if ( m_hObstacle != OBSTACLE_INVALID )
{
CAI_LocalNavigator::RemoveGlobalObstacle( m_hObstacle );
m_hObstacle = OBSTACLE_INVALID;
}
BaseClass::UpdateOnRemove();
}
void CEntityFlame::Precache()
{
BaseClass::Precache();
#ifndef DOTA_DLL
PrecacheParticleSystem( "burning_character" );
PrecacheParticleSystem( "burning_gib_01" );
PrecacheScriptSound( "General.StopBurning" );
PrecacheScriptSound( "General.BurningFlesh" );
PrecacheScriptSound( "General.BurningObject" );
#endif
}
void CEntityFlame::Spawn()
{
BaseClass::Spawn();
m_flLifetime = gpGlobals->curtime;
SetThink( &CEntityFlame::FlameThink );
SetNextThink( gpGlobals->curtime + 0.1f );
//Send to the client even though we don't have a model
AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
#ifdef HL2_EP3
m_iDangerSound = CSoundEnt::InsertSound( SOUND_DANGER | SOUND_CONTEXT_FROM_FIRE | SOUND_CONTEXT_FOLLOW_OWNER,
GetAbsOrigin(), m_flSize * 2.0f, FLT_MAX, this );
#endif
}
//-----------------------------------------------------------------------------
// Since we don't save/load danger links, we need to reacquire them here
//-----------------------------------------------------------------------------
void CEntityFlame::Activate()
{
BaseClass::Activate();
#ifdef HL2_EP3
#if 0
// Mark nearby links as dangerous
const Vector &vecOrigin = GetAbsOrigin();
float flMaxDistSqr = m_flSize * m_flSize;
m_hObstacle = CAI_LocalNavigator::AddGlobalObstacle( vecOrigin, m_flSize, AIMST_AVOID_DANGER );
for ( int i = 0; i < g_pBigAINet->NumNodes(); i++ )
{
CAI_Node *pSrcNode = g_pBigAINet->GetNode( i );
int nSrcNodeId = pSrcNode->GetId();
for ( int j = 0; j < pSrcNode->NumLinks(); j++ )
{
CAI_Link *pLink = pSrcNode->GetLinkByIndex( j );
int nDstNodeId = pLink->DestNodeID( nSrcNodeId );
// Eliminates double-checking of links
if ( nDstNodeId < nSrcNodeId )
continue;
CAI_Node *pDstNode = g_pBigAINet->GetNode( nDstNodeId );
float flDistSqr = CalcDistanceSqrToLineSegment( vecOrigin, pSrcNode->GetOrigin(), pDstNode->GetOrigin() );
if ( flDistSqr > flMaxDistSqr )
continue;
++pLink->m_nDangerCount;
m_DangerLinks.AddToTail( pLink );
}
}
#endif
#endif // HL2_EP3
}
void CEntityFlame::UseCheapEffect( bool bCheap )
{
m_bCheapEffect = bCheap;
}
//-----------------------------------------------------------------------------
// Purpose: Creates a flame and attaches it to a target entity.
// Input : pTarget -
//-----------------------------------------------------------------------------
CEntityFlame *CEntityFlame::Create( CBaseEntity *pTarget, float flLifetime, float flSize /*= 0.0f*/, bool bUseHitboxes /*= true*/ )
{
CEntityFlame *pFlame = (CEntityFlame *)CreateEntityByName( "entityflame" );
if ( pFlame == NULL )
return NULL;
if ( flSize <= 0.0f )
{
float xSize = pTarget->CollisionProp()->OBBMaxs().x - pTarget->CollisionProp()->OBBMins().x;
float ySize = pTarget->CollisionProp()->OBBMaxs().y - pTarget->CollisionProp()->OBBMins().y;
flSize = ( xSize + ySize ) * 0.5f;
if ( flSize < 16.0f )
{
flSize = 16.0f;
}
}
if ( flLifetime <= 0.0f )
{
flLifetime = 2.0f;
}
pFlame->m_flSize = flSize;
pFlame->Spawn();
UTIL_SetOrigin( pFlame, pTarget->GetAbsOrigin() );
pFlame->AttachToEntity( pTarget );
pFlame->SetLifetime( flLifetime );
pFlame->Activate();
return pFlame;
}
//-----------------------------------------------------------------------------
// Purpose: Attaches the flame to an entity and moves with it
// Input : pTarget - target entity to attach to
//-----------------------------------------------------------------------------
void CEntityFlame::AttachToEntity( CBaseEntity *pTarget )
{
// For networking to the client.
m_hEntAttached = pTarget;
if( pTarget->IsNPC() )
{
EmitSound( "General.BurningFlesh" );
}
else
{
EmitSound( "General.BurningObject" );
}
m_bPlayingSound = true;
// So our heat emitter follows the entity around on the server.
SetParent( pTarget );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : lifetime -
//-----------------------------------------------------------------------------
void CEntityFlame::SetLifetime( float lifetime )
{
m_flLifetime = gpGlobals->curtime + lifetime;
}
float CEntityFlame::GetRemainingLife( void ) const
{
return m_flLifetime - gpGlobals->curtime;
}
//-----------------------------------------------------------------------------
// Purpose: Burn targets around us
//-----------------------------------------------------------------------------
void CEntityFlame::FlameThink( void )
{
// Assure that this function will be ticked again even if we early-out in the if below.
SetNextThink( gpGlobals->curtime + FLAME_DAMAGE_INTERVAL );
if ( !m_hEntAttached.Get() )
{
UTIL_Remove( this );
return;
}
if ( m_hEntAttached->GetFlags() & FL_TRANSRAGDOLL )
{
SetRenderAlpha( 0 );
return;
}
CAI_BaseNPC *pNPC = m_hEntAttached->MyNPCPointer();
if ( pNPC && !pNPC->IsAlive() )
{
UTIL_Remove( this );
// Notify the NPC that it's no longer burning!
pNPC->Extinguish();
return;
}
if ( m_hEntAttached->GetWaterLevel() > WL_NotInWater )
{
Vector mins, maxs;
mins = m_hEntAttached->WorldSpaceCenter();
maxs = mins;
maxs.z = m_hEntAttached->WorldSpaceCenter().z;
maxs.x += 32;
maxs.y += 32;
mins.z -= 32;
mins.x -= 32;
mins.y -= 32;
UTIL_Bubbles( mins, maxs, 12 );
}
// See if we're done burning, or our attached ent has vanished
if ( m_flLifetime < gpGlobals->curtime || m_hEntAttached == NULL )
{
EmitSound( "General.StopBurning" );
m_bPlayingSound = false;
SetThink( &CEntityFlame::SUB_Remove );
SetNextThink( gpGlobals->curtime + 0.5f );
// Notify anything we're attached to
if ( m_hEntAttached )
{
CBaseCombatCharacter *pAttachedCC = m_hEntAttached->MyCombatCharacterPointer();
if( pAttachedCC )
{
// Notify the NPC that it's no longer burning!
pAttachedCC->Extinguish();
}
}
return;
}
if ( m_hEntAttached )
{
// Do radius damage ignoring the entity I'm attached to. This will harm things around me.
RadiusDamage( CTakeDamageInfo( this, this, 4.0f, DMG_BURN ), GetAbsOrigin(), m_flSize/2, CLASS_NONE, m_hEntAttached );
// Directly harm the entity I'm attached to. This is so we can precisely control how much damage the entity
// that is on fire takes without worrying about the flame's position relative to the bodytarget (which is the
// distance that the radius damage code uses to determine how much damage to inflict)
m_hEntAttached->TakeDamage( CTakeDamageInfo( this, this, FLAME_DIRECT_DAMAGE, DMG_BURN | DMG_DIRECT ) );
if( !m_hEntAttached->IsNPC() && hl2_episodic.GetBool() )
{
const float ENTITYFLAME_MOVE_AWAY_DIST = 24.0f;
// Make a sound near my origin, and up a little higher (in case I'm on the ground, so NPC's still hear it)
CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin(), ENTITYFLAME_MOVE_AWAY_DIST, 0.1f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
CSoundEnt::InsertSound( SOUND_MOVE_AWAY, GetAbsOrigin() + Vector( 0, 0, 48.0f ), ENTITYFLAME_MOVE_AWAY_DIST, 0.1f, this, SOUNDENT_CHANNEL_REPEATING );
}
}
else
{
RadiusDamage( CTakeDamageInfo( this, this, FLAME_RADIUS_DAMAGE, DMG_BURN ), GetAbsOrigin(), m_flSize/2, CLASS_NONE, NULL );
}
FireSystem_AddHeatInRadius( GetAbsOrigin(), m_flSize/2, 2.0f );
}
//-----------------------------------------------------------------------------
// Igniter
//-----------------------------------------------------------------------------
class CEnvEntityIgniter : public CBaseEntity
{
public:
DECLARE_CLASS( CEnvEntityIgniter, CBaseEntity );
DECLARE_DATADESC();
virtual void Precache();
protected:
void InputIgnite( inputdata_t &inputdata );
float m_flLifetime;
};
BEGIN_DATADESC( CEnvEntityIgniter )
DEFINE_KEYFIELD( m_flLifetime, FIELD_FLOAT, "lifetime" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Ignite", InputIgnite ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( env_entity_igniter, CEnvEntityIgniter );
//-----------------------------------------------------------------------------
// Purpose: Ignites entities
//-----------------------------------------------------------------------------
void CEnvEntityIgniter::Precache()
{
BaseClass::Precache();
UTIL_PrecacheOther( "entityflame" );
}
//-----------------------------------------------------------------------------
// Purpose: Ignites entities
//-----------------------------------------------------------------------------
void CEnvEntityIgniter::InputIgnite( inputdata_t &inputdata )
{
if ( m_target == NULL_STRING )
return;
CBaseEntity *pTarget = NULL;
while ( (pTarget = gEntList.FindEntityGeneric(pTarget, STRING(m_target), this, inputdata.pActivator)) != NULL )
{
// Combat characters know how to catch themselves on fire.
CBaseCombatCharacter *pBCC = pTarget->MyCombatCharacterPointer();
if (pBCC)
{
// DVS TODO: consider promoting Ignite to CBaseEntity and doing everything here
pBCC->Ignite( m_flLifetime );
continue;
}
// Everything else, we handle here.
CEntityFlame::Create( pTarget, m_flLifetime );
}
}
+85
View File
@@ -0,0 +1,85 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#ifndef ENTITYFLAME_H
#define ENTITYFLAME_H
#ifdef _WIN32
#pragma once
#endif
#include "ai_planesolver.h"
#define FLAME_DAMAGE_INTERVAL 0.2f // How often to deal damage.
#define FLAME_DIRECT_DAMAGE_PER_SEC 5.0f
#define FLAME_RADIUS_DAMAGE_PER_SEC 4.0f
#define FLAME_DIRECT_DAMAGE ( FLAME_DIRECT_DAMAGE_PER_SEC * FLAME_DAMAGE_INTERVAL )
#define FLAME_RADIUS_DAMAGE ( FLAME_RADIUS_DAMAGE_PER_SEC * FLAME_DAMAGE_INTERVAL )
#define FLAME_MAX_LIFETIME_ON_DEAD_NPCS 10.0f
class CAI_Link;
class CEntityFlame : public CBaseEntity
{
DECLARE_SERVERCLASS();
DECLARE_CLASS( CEntityFlame, CBaseEntity );
DECLARE_DATADESC();
public:
static CEntityFlame *Create( CBaseEntity *pTarget, float flLifetime, float flSize = 0.0f, bool bUseHitboxes = true );
CEntityFlame( void );
void AttachToEntity( CBaseEntity *pTarget );
void SetLifetime( float lifetime );
void SetUseHitboxes( bool use );
void SetNumHitboxFires( int iNumHitBoxFires );
void SetHitboxFireScale( float flHitboxFireScale );
float GetRemainingLife( void ) const;
int GetNumHitboxFires( void );
float GetHitboxFireScale( void );
virtual void Precache();
virtual void UpdateOnRemove();
virtual void Spawn();
virtual void Activate();
void UseCheapEffect( bool bCheap );
void SetSize( float size ) { m_flSize = size; }
void SetAttacker( CBaseEntity *pAttacker ) { m_hAttacker = pAttacker; }
CBaseEntity *GetAttacker( void ) const;
protected:
void FlameThink( void );
CNetworkHandle( CBaseEntity, m_hEntAttached ); // The entity that we are burning (attached to).
CNetworkVar( bool, m_bCheapEffect );
CNetworkVar( float, m_flSize );
CNetworkVar( bool, m_bUseHitboxes );
CNetworkVar( int, m_iNumHitboxFires );
CNetworkVar( float, m_flHitboxFireScale );
CNetworkVar( float, m_flLifetime );
string_t m_iszPlayingSound; // Track the sound so we can StopSound later
EHANDLE m_hAttacker;
int m_iDangerSound;
bool m_bPlayingSound;
CUtlVector< CAI_Link * > m_DangerLinks;
Obstacle_t m_hObstacle;
};
#endif // ENTITYFLAME_H
+163
View File
@@ -0,0 +1,163 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Dissolve entity to be attached to target entity. Serves two purposes:
//
// 1) An entity that can be placed by a level designer and triggered
// to ignite a target entity.
//
// 2) An entity that can be created at runtime to ignite a target entity.
//
//=============================================================================//
#include "cbase.h"
#include "EntityFreezing.h"
#include "baseanimating.h"
#include "ai_basenpc.h"
#include "dt_utlvector_send.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static const char *s_pElectroThinkContext = "ElectroThinkContext";
//-----------------------------------------------------------------------------
// Model
//-----------------------------------------------------------------------------
#define FREEZING_SPRITE_NAME "sprites/blueglow1.vmt"
//-----------------------------------------------------------------------------
// Save/load
//-----------------------------------------------------------------------------
BEGIN_DATADESC( CEntityFreezing )
DEFINE_FIELD( m_vFreezingOrigin, FIELD_VECTOR ),
DEFINE_AUTO_ARRAY( m_flFrozenPerHitbox, FIELD_FLOAT ),
DEFINE_KEYFIELD( m_flFrozen, FIELD_FLOAT, "frozen" ),
DEFINE_FIELD( m_bFinishFreezing, FIELD_BOOLEAN ),
DEFINE_INPUTFUNC( FIELD_STRING, "Freeze", InputFreeze ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Networking
//-----------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( CEntityFreezing, DT_EntityFreezing )
SendPropVector( SENDINFO(m_vFreezingOrigin), 0, SPROP_NOSCALE ),
SendPropArray3( SENDINFO_ARRAY3(m_flFrozenPerHitbox), SendPropFloat( SENDINFO_ARRAY(m_flFrozenPerHitbox) ) ),
SendPropFloat( SENDINFO( m_flFrozen ) ),
SendPropBool( SENDINFO( m_bFinishFreezing ) ),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( env_entity_freezing, CEntityFreezing );
PRECACHE_REGISTER( env_entity_freezing );
//-----------------------------------------------------------------------------
// Precache
//-----------------------------------------------------------------------------
void CEntityFreezing::Precache()
{
if ( NULL_STRING != GetModelName() )
{
PrecacheModel( STRING( GetModelName() ) );
}
#ifdef USE_BLOBULATOR
PrecacheMaterial( "models/weapons/w_icegun/ice_surface" );
#endif
}
//-----------------------------------------------------------------------------
// Spawn
//-----------------------------------------------------------------------------
void CEntityFreezing::Spawn()
{
BaseClass::Spawn();
Precache();
UTIL_SetModel( this, STRING( GetModelName() ) );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : inputdata -
//-----------------------------------------------------------------------------
void CEntityFreezing::InputFreeze( inputdata_t &inputdata )
{
string_t strTarget = inputdata.value.StringID();
if (strTarget == NULL_STRING)
{
strTarget = m_target;
}
CBaseEntity *pTarget = NULL;
while ((pTarget = gEntList.FindEntityGeneric(pTarget, STRING(strTarget), this, inputdata.pActivator)) != NULL)
{
CBaseAnimating *pBaseAnim = pTarget->GetBaseAnimating();
if ( pBaseAnim )
{
pBaseAnim->Freeze( m_flFrozen );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Creates a flame and attaches it to a target entity.
// Input : pTarget -
//-----------------------------------------------------------------------------
CEntityFreezing *CEntityFreezing::Create( CBaseAnimating *pTarget )
{
if ( pTarget )
{
if ( pTarget->IsPlayer() )
{
// Simply immediately kill the player.
CBasePlayer *pPlayer = assert_cast< CBasePlayer* >( pTarget );
pPlayer->SetArmorValue( 0 );
CTakeDamageInfo info( pPlayer, pPlayer, pPlayer->GetHealth(), DMG_GENERIC | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE );
pPlayer->TakeDamage( info );
return NULL;
}
}
CEntityFreezing *pFreezing = (CEntityFreezing *) CreateEntityByName( "env_entity_freezing" );
if ( pFreezing == NULL )
return NULL;
if ( pTarget )
{
pFreezing->AttachToEntity( pTarget );
pFreezing->SetModelName( pTarget->GetModelName() );
pFreezing->SetFrozen( pTarget->GetFrozenAmount() );
}
DispatchSpawn( pFreezing );
// Send to the client even though we don't have a model
pFreezing->AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
return pFreezing;
}
//-----------------------------------------------------------------------------
// Purpose: Attaches the flame to an entity and moves with it
// Input : pTarget - target entity to attach to
//-----------------------------------------------------------------------------
void CEntityFreezing::AttachToEntity( CBaseEntity *pTarget )
{
// So our dissolver follows the entity around on the server.
SetParent( pTarget );
SetLocalOrigin( vec3_origin );
SetLocalAngles( vec3_angle );
}
+45
View File
@@ -0,0 +1,45 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef ENTITYFREEZING_H
#define ENTITYFREEZING_H
#ifdef _WIN32
#pragma once
#endif
class CEntityFreezing : public CBaseEntity
{
public:
DECLARE_SERVERCLASS();
DECLARE_CLASS( CEntityFreezing, CBaseEntity );
static CEntityFreezing *Create( CBaseAnimating *pTarget );
void Precache();
void Spawn();
void AttachToEntity( CBaseEntity *pTarget );
void SetFreezingOrigin( Vector vOrigin ) { m_vFreezingOrigin = vOrigin; }
Vector GetFreezingOrigin( void ) { return m_vFreezingOrigin; }
void SetFrozen( float flFrozen ){ m_flFrozen = flFrozen; }
int GetFrozen( void ) { return m_flFrozen; }
void FinishFreezing( void ) { m_bFinishFreezing = true; }
DECLARE_DATADESC();
protected:
void InputFreeze( inputdata_t &inputdata );
CNetworkVector( m_vFreezingOrigin );
CNetworkVar( float, m_flFrozen );
CNetworkVar( bool, m_bFinishFreezing );
public:
CNetworkArray( float, m_flFrozenPerHitbox, 50 );
};
#endif // ENTITYFREEZING_H
+207
View File
@@ -0,0 +1,207 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Drops particles where the entity was.
//
//=============================================================================//
#include "cbase.h"
#include "EntityParticleTrail.h"
#include "networkstringtable_gamedll.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Used to retire the entity
//-----------------------------------------------------------------------------
static const char *s_pRetireContext = "RetireContext";
//-----------------------------------------------------------------------------
// Save/load
//-----------------------------------------------------------------------------
BEGIN_DATADESC( CEntityParticleTrail )
DEFINE_FIELD( m_iMaterialName, FIELD_MATERIALINDEX ),
DEFINE_EMBEDDED( m_Info ),
DEFINE_FIELD( m_hConstraintEntity, FIELD_EHANDLE ),
// Think this should be handled by StartTouch/etc.
// DEFINE_FIELD( m_nRefCount, FIELD_INTEGER ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Networking
//-----------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( CEntityParticleTrail, DT_EntityParticleTrail )
SendPropInt(SENDINFO(m_iMaterialName), MAX_MATERIAL_STRING_BITS, SPROP_UNSIGNED ),
SendPropDataTable( SENDINFO_DT( m_Info ), &REFERENCE_SEND_TABLE( DT_EntityParticleTrailInfo ) ),
SendPropEHandle(SENDINFO(m_hConstraintEntity)),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( env_particle_trail, CEntityParticleTrail );
//-----------------------------------------------------------------------------
// Purpose: Creates a flame and attaches it to a target entity.
// Input : pTarget -
//-----------------------------------------------------------------------------
CEntityParticleTrail *CEntityParticleTrail::Create( CBaseEntity *pTarget, const EntityParticleTrailInfo_t &info, CBaseEntity *pConstraintEntity )
{
int iMaterialName = GetMaterialIndex( STRING(info.m_strMaterialName) );
// Look for other particle trails on the entity + copy state to the new entity
CEntityParticleTrail *pTrail;
CBaseEntity *pNext;
for ( CBaseEntity *pChild = pTarget->FirstMoveChild(); pChild; pChild = pNext )
{
pNext = pChild->NextMovePeer();
pTrail = dynamic_cast<CEntityParticleTrail*>(pChild);
if ( pTrail && (pTrail->m_iMaterialName == iMaterialName) )
{
// Prevent destruction if it re-enters the field
pTrail->IncrementRefCount();
return pTrail;
}
}
pTrail = (CEntityParticleTrail *)CreateEntityByName( "env_particle_trail" );
if ( pTrail == NULL )
return NULL;
pTrail->m_hConstraintEntity = pConstraintEntity;
pTrail->m_iMaterialName = iMaterialName;
pTrail->m_Info.CopyFrom(info);
pTrail->m_nRefCount = 1;
pTrail->AttachToEntity( pTarget );
pTrail->Spawn();
return pTrail;
}
//-----------------------------------------------------------------------------
// Spawn
//-----------------------------------------------------------------------------
void CEntityParticleTrail::Spawn()
{
BaseClass::Spawn();
/*
SetThink( &CEntityParticleTrail::BoogieThink );
SetNextThink( gpGlobals->curtime + 0.01f );
if ( HasSpawnFlags( SF_RAGDOLL_BOOGIE_ELECTRICAL ) )
{
SetContextThink( ZapThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), s_pZapContext );
}
*/
}
//-----------------------------------------------------------------------------
// Spawn
//-----------------------------------------------------------------------------
void CEntityParticleTrail::UpdateOnRemove()
{
g_pNotify->ClearEntity( this );
BaseClass::UpdateOnRemove();
}
//-----------------------------------------------------------------------------
// Force our constraint entity to be trasmitted
//-----------------------------------------------------------------------------
void CEntityParticleTrail::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways )
{
// Are we already marked for transmission?
if ( pInfo->m_pTransmitEdict->Get( entindex() ) )
return;
BaseClass::SetTransmit( pInfo, bAlways );
// Force our constraint entity to be sent too.
if ( m_hConstraintEntity )
{
m_hConstraintEntity->SetTransmit( pInfo, bAlways );
}
}
//-----------------------------------------------------------------------------
// Retire
//-----------------------------------------------------------------------------
void CEntityParticleTrail::IncrementRefCount()
{
if ( m_nRefCount == 0 )
{
SetContextThink( NULL, gpGlobals->curtime, s_pRetireContext );
}
++m_nRefCount;
}
void CEntityParticleTrail::DecrementRefCount()
{
--m_nRefCount;
Assert( m_nRefCount >= 0 );
if ( m_nRefCount == 0 )
{
FollowEntity( NULL );
g_pNotify->ClearEntity( this );
SetContextThink( &CEntityParticleTrail::SUB_Remove, gpGlobals->curtime + m_Info.m_flLifetime, s_pRetireContext );
}
}
//-----------------------------------------------------------------------------
// Clean up when the entity goes away.
//-----------------------------------------------------------------------------
void CEntityParticleTrail::NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
{
BaseClass::NotifySystemEvent( pNotify, eventType, params );
Assert( pNotify == GetMoveParent() );
if ( eventType == NOTIFY_EVENT_DESTROY )
{
FollowEntity( NULL );
g_pNotify->ClearEntity( this );
if ( m_nRefCount != 0 )
{
m_nRefCount = 0;
SetContextThink( &CEntityParticleTrail::SUB_Remove, gpGlobals->curtime + m_Info.m_flLifetime, s_pRetireContext );
}
}
}
//-----------------------------------------------------------------------------
// Suppression count
//-----------------------------------------------------------------------------
void CEntityParticleTrail::Destroy( CBaseEntity *pTarget, const EntityParticleTrailInfo_t &info )
{
int iMaterialName = GetMaterialIndex( STRING(info.m_strMaterialName) );
// Look for the particle trail attached to this entity + decrease refcount
CBaseEntity *pNext;
for ( CBaseEntity *pChild = pTarget->FirstMoveChild(); pChild; pChild = pNext )
{
pNext = pChild->NextMovePeer();
CEntityParticleTrail *pTrail = dynamic_cast<CEntityParticleTrail*>(pChild);
if ( !pTrail || (pTrail->m_iMaterialName != iMaterialName) )
continue;
pTrail->DecrementRefCount();
}
}
//-----------------------------------------------------------------------------
// Attach to an entity
//-----------------------------------------------------------------------------
void CEntityParticleTrail::AttachToEntity( CBaseEntity *pTarget )
{
FollowEntity( pTarget );
g_pNotify->AddEntity( this, pTarget );
}
+52
View File
@@ -0,0 +1,52 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef ENTITYPARTICLETRAIL_H
#define ENTITYPARTICLETRAIL_H
#ifdef _WIN32
#pragma once
#endif
#include "baseparticleentity.h"
#include "entityparticletrail_shared.h"
//-----------------------------------------------------------------------------
// Spawns particles after the entity
//-----------------------------------------------------------------------------
class CEntityParticleTrail : public CBaseParticleEntity
{
DECLARE_DATADESC();
DECLARE_CLASS( CEntityParticleTrail, CBaseParticleEntity );
DECLARE_SERVERCLASS();
public:
static CEntityParticleTrail *Create( CBaseEntity *pTarget, const EntityParticleTrailInfo_t &info, CBaseEntity *pConstraint );
static void Destroy( CBaseEntity *pTarget, const EntityParticleTrailInfo_t &info );
void Spawn();
virtual void UpdateOnRemove();
// Force our constraint entity to be trasmitted
virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways );
// Clean up when the entity goes away.
virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
private:
void AttachToEntity( CBaseEntity *pTarget );
void IncrementRefCount();
void DecrementRefCount();
CNetworkVar( int, m_iMaterialName );
CNetworkVarEmbedded( EntityParticleTrailInfo_t, m_Info );
CNetworkHandle( CBaseEntity, m_hConstraintEntity );
int m_nRefCount;
};
#endif // ENTITYPARTICLETRAIL_H
+816
View File
@@ -0,0 +1,816 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements visual effects entities: sprites, beams, bubbles, etc.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "beam_shared.h"
#include "ndebugoverlay.h"
#include "filters.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// Keeps us from doing strcmps in the tracefilter.
string_t g_iszPhysicsPropClassname;
enum Touch_t
{
touch_none = 0,
touch_player_only,
touch_npc_only,
touch_player_or_npc,
touch_player_or_npc_or_physicsprop,
};
class CEnvBeam : public CBeam
{
public:
DECLARE_CLASS( CEnvBeam, CBeam );
void Spawn( void );
void Precache( void );
void Activate( void );
void StrikeThink( void );
void UpdateThink( void );
void RandomArea( void );
void RandomPoint( const Vector &vecSrc );
void Zap( const Vector &vecSrc, const Vector &vecDest );
void Strike( void );
bool PassesTouchFilters(CBaseEntity *pOther);
void InputTurnOn( inputdata_t &inputdata );
void InputTurnOff( inputdata_t &inputdata );
void InputToggle( inputdata_t &inputdata );
void InputStrikeOnce( inputdata_t &inputdata );
void TurnOn( void );
void TurnOff( void );
void Toggle( void );
const char *GetDecalName( void ){ return STRING( m_iszDecal );}
inline bool ServerSide( void )
{
if ( m_life == 0 && !HasSpawnFlags(SF_BEAM_RING) )
return true;
return false;
}
DECLARE_DATADESC();
void BeamUpdateVars( void );
protected:
// true if the end point vecline was specified in hammer
inline bool HasEndPointHandle() { return !m_vEndPointRelative.IsZero(); }
int m_active;
int m_spriteTexture;
string_t m_iszStartEntity;
string_t m_iszEndEntity;
float m_life;
float m_boltWidth;
float m_noiseAmplitude;
int m_speed;
float m_restrike;
string_t m_iszSpriteName;
int m_frameStart;
// endpoint may be optionally specified as a vecline instead of a target entity.
// note: this mechanism seems rather roundabout, because the parent CBeam has
// the m_vecEndPos data member; however, the CEnvBeam is programmed to overwrite it
// each frame with the position of a target entity, so the easiest way to
// implement this behavior was to put this bit of redundant data in the child
// class and have it get written back every frame. If this bothers you,
// please fix it.
Vector m_vEndPointWorld; // this is the point as read from the level spec; however it's not used, because
Vector m_vEndPointRelative; // on spawn, endpoint is transformed into local space here.
float m_radius;
Touch_t m_TouchType;
string_t m_iFilterName;
EHANDLE m_hFilter;
string_t m_iszDecal;
COutputEvent m_OnTouchedByEntity;
};
LINK_ENTITY_TO_CLASS( env_beam, CEnvBeam );
BEGIN_DATADESC( CEnvBeam )
DEFINE_FIELD( m_active, FIELD_INTEGER ),
DEFINE_FIELD( m_spriteTexture, FIELD_INTEGER ),
DEFINE_KEYFIELD( m_iszStartEntity, FIELD_STRING, "LightningStart" ),
DEFINE_KEYFIELD( m_iszEndEntity, FIELD_STRING, "LightningEnd" ),
DEFINE_KEYFIELD( m_vEndPointWorld, FIELD_VECTOR, "targetpoint" ),
DEFINE_KEYFIELD( m_life, FIELD_FLOAT, "life" ),
DEFINE_KEYFIELD( m_boltWidth, FIELD_FLOAT, "BoltWidth" ),
DEFINE_KEYFIELD( m_noiseAmplitude, FIELD_FLOAT, "NoiseAmplitude" ),
DEFINE_KEYFIELD( m_speed, FIELD_INTEGER, "TextureScroll" ),
DEFINE_KEYFIELD( m_restrike, FIELD_FLOAT, "StrikeTime" ),
DEFINE_KEYFIELD( m_iszSpriteName, FIELD_STRING, "texture" ),
DEFINE_KEYFIELD( m_frameStart, FIELD_INTEGER, "framestart" ),
DEFINE_KEYFIELD( m_radius, FIELD_FLOAT, "Radius" ),
DEFINE_KEYFIELD( m_TouchType, FIELD_INTEGER, "TouchType" ),
DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ),
DEFINE_KEYFIELD( m_iszDecal, FIELD_STRING, "decalname" ),
DEFINE_KEYFIELD( m_nClipStyle, FIELD_INTEGER, "ClipStyle" ),
DEFINE_FIELD( m_hFilter, FIELD_EHANDLE ),
// Function Pointers
DEFINE_FUNCTION( StrikeThink ),
DEFINE_FUNCTION( UpdateThink ),
// Input functions
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
DEFINE_INPUTFUNC( FIELD_VOID, "StrikeOnce", InputStrikeOnce ),
DEFINE_OUTPUT( m_OnTouchedByEntity, "OnTouchedByEntity" ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvBeam::Spawn( void )
{
if ( !m_iszSpriteName )
{
SetThink( &CEnvBeam::SUB_Remove );
return;
}
BaseClass::Spawn();
m_noiseAmplitude = MIN(MAX_BEAM_NOISEAMPLITUDE, m_noiseAmplitude);
// Check for tapering
if ( HasSpawnFlags( SF_BEAM_TAPEROUT ) )
{
SetWidth( m_boltWidth );
SetEndWidth( 0 );
}
else
{
SetWidth( m_boltWidth );
SetEndWidth( GetWidth() ); // Note: EndWidth is not scaled
}
// if a non-targetentity endpoint was specified, transform it into local relative space
// so it can move along with the base
if (!m_vEndPointWorld.IsZero())
{
WorldToEntitySpace( m_vEndPointWorld, &m_vEndPointRelative );
}
else
{
m_vEndPointRelative.Zero();
}
if ( ServerSide() )
{
SetThink( &CEnvBeam::UpdateThink );
SetNextThink( gpGlobals->curtime );
SetFireTime( gpGlobals->curtime );
if ( GetEntityName() != NULL_STRING )
{
if ( !(m_spawnflags & SF_BEAM_STARTON) )
{
AddEffects( EF_NODRAW );
m_active = 0;
SetNextThink( TICK_NEVER_THINK );
}
else
{
m_active = 1;
}
}
}
else
{
m_active = 0;
if ( !GetEntityName() || FBitSet(m_spawnflags, SF_BEAM_STARTON) )
{
SetThink( &CEnvBeam::StrikeThink );
SetNextThink( gpGlobals->curtime + 1.0f );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvBeam::Precache( void )
{
if ( !Q_stristr( STRING(m_iszSpriteName), ".vmt" ) )
{
// HACK/YWB: This was almost always the laserbeam.spr, so alloc'ing the name a second time with the proper extension isn't going to
// kill us on memrory.
//Warning( "Level Design Error: %s (%i:%s) Sprite name (%s) missing .vmt extension!\n",
// STRING( m_iClassname ), entindex(), GetEntityName(), STRING(m_iszSpriteName) );
char fixedname[ 512 ];
Q_strncpy( fixedname, STRING( m_iszSpriteName ), sizeof( fixedname ) );
Q_SetExtension( fixedname, ".vmt", sizeof( fixedname ) );
m_iszSpriteName = AllocPooledString( fixedname );
}
g_iszPhysicsPropClassname = AllocPooledString( "prop_physics" );
m_spriteTexture = PrecacheModel( STRING(m_iszSpriteName) );
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvBeam::Activate( void )
{
// Get a handle to my filter entity if there is one
if (m_iFilterName != NULL_STRING)
{
m_hFilter = dynamic_cast<CBaseFilter *>(gEntList.FindEntityByName( NULL, m_iFilterName ));
}
BaseClass::Activate();
if ( ServerSide() )
BeamUpdateVars();
}
//-----------------------------------------------------------------------------
// Purpose: Input handler to turn the lightning on either continually or for
// interval refiring.
//-----------------------------------------------------------------------------
void CEnvBeam::InputTurnOn( inputdata_t &inputdata )
{
if ( !m_active )
{
TurnOn();
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler to turn the lightning off.
//-----------------------------------------------------------------------------
void CEnvBeam::InputTurnOff( inputdata_t &inputdata )
{
if ( m_active )
{
TurnOff();
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler to toggle the lightning on/off.
//-----------------------------------------------------------------------------
void CEnvBeam::InputToggle( inputdata_t &inputdata )
{
if ( m_active )
{
TurnOff();
}
else
{
TurnOn();
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for making the beam strike once. This will not affect
// any interval refiring that might be going on. If the lifetime is set
// to zero (infinite) it will turn on and stay on.
//-----------------------------------------------------------------------------
void CEnvBeam::InputStrikeOnce( inputdata_t &inputdata )
{
Strike();
}
//-----------------------------------------------------------------------------
// Purpose: Turns the lightning on. If it is set for interval refiring, it will
// begin doing so. If it is set to be continually on, it will do so.
//-----------------------------------------------------------------------------
void CEnvBeam::TurnOn( void )
{
m_active = 1;
if ( ServerSide() )
{
RemoveEffects( EF_NODRAW );
DoSparks( GetAbsStartPos(), GetAbsEndPos() );
SetThink( &CEnvBeam::UpdateThink );
SetNextThink( gpGlobals->curtime );
SetFireTime( gpGlobals->curtime );
}
else
{
SetThink( &CEnvBeam::StrikeThink );
SetNextThink( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvBeam::TurnOff( void )
{
m_active = 0;
if ( ServerSide() )
{
AddEffects( EF_NODRAW );
}
SetNextThink( TICK_NEVER_THINK );
SetThink( NULL );
}
//-----------------------------------------------------------------------------
// Purpose: Think function for striking at intervals.
//-----------------------------------------------------------------------------
void CEnvBeam::StrikeThink( void )
{
if ( m_life != 0 )
{
if ( m_spawnflags & SF_BEAM_RANDOM )
SetNextThink( gpGlobals->curtime + m_life + random->RandomFloat( 0, m_restrike ) );
else
SetNextThink( gpGlobals->curtime + m_life + m_restrike );
}
m_active = 1;
if (!m_iszEndEntity && !HasEndPointHandle())
{
if (!m_iszStartEntity)
{
RandomArea( );
}
else
{
CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) );
if (pStart != NULL)
{
RandomPoint( pStart->GetAbsOrigin() );
}
else
{
Msg( "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) );
}
}
return;
}
Strike();
}
//-----------------------------------------------------------------------------
// Purpose: Strikes once for its configured lifetime.
//-----------------------------------------------------------------------------
void CEnvBeam::Strike( void )
{
CBroadcastRecipientFilter filter;
CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) );
CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) );
// if the end entity is missing, we use the Hammer-specified vector offset instead.
bool bEndPointFromEntity = pEnd != NULL;
if ( pStart == NULL || ( !bEndPointFromEntity && !HasEndPointHandle() ) )
return;
Vector vEndPointLocation;
if ( bEndPointFromEntity )
{
vEndPointLocation = pEnd->GetAbsOrigin() ;
}
else
{
EntityToWorldSpace( m_vEndPointRelative, &vEndPointLocation );
}
m_speed = clamp( m_speed, 0, MAX_BEAM_SCROLLSPEED );
bool pointStart = IsStaticPointEntity( pStart );
bool pointEnd = !bEndPointFromEntity || IsStaticPointEntity( pEnd );
if ( pointStart || pointEnd )
{
if ( m_spawnflags & SF_BEAM_RING )
{
// don't work
return;
}
te->BeamEntPoint( filter, 0.0,
pointStart ? 0 : pStart->entindex(),
pointStart ? &pStart->GetAbsOrigin() : NULL,
pointEnd ? 0 : pEnd->entindex(),
pointEnd ? &vEndPointLocation : NULL,
m_spriteTexture,
0, // No halo
m_frameStart,
(int)m_flFrameRate,
m_life,
m_boltWidth,
m_boltWidth, // End width
0, // No fade
m_noiseAmplitude,
m_clrRender->r, m_clrRender->g, m_clrRender->b, m_clrRender->a,
m_speed );
}
else
{
if ( m_spawnflags & SF_BEAM_RING)
{
te->BeamRing( filter, 0.0,
pStart->entindex(),
pEnd->entindex(),
m_spriteTexture,
0, // No halo
m_frameStart,
(int)m_flFrameRate,
m_life,
m_boltWidth,
0, // No spread
m_noiseAmplitude,
m_clrRender->r,
m_clrRender->g,
m_clrRender->b,
m_clrRender->a,
m_speed );
}
else
{
te->BeamEnts( filter, 0.0,
pStart->entindex(),
pEnd->entindex(),
m_spriteTexture,
0, // No halo
m_frameStart,
(int)m_flFrameRate,
m_life,
m_boltWidth,
m_boltWidth, // End width
0, // No fade
m_noiseAmplitude,
m_clrRender->r,
m_clrRender->g,
m_clrRender->b,
m_clrRender->a,
m_speed );
}
}
DoSparks( pStart->GetAbsOrigin(), pEnd->GetAbsOrigin() );
if ( m_flDamage > 0 )
{
trace_t tr;
UTIL_TraceLine( pStart->GetAbsOrigin(), pEnd->GetAbsOrigin(), MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr );
BeamDamageInstant( &tr, m_flDamage );
}
}
class CTraceFilterPlayersNPCs : public ITraceFilter
{
public:
bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
if ( pEntity )
{
if ( pEntity->IsPlayer() || pEntity->MyNPCPointer() )
return true;
}
return false;
}
virtual TraceType_t GetTraceType() const
{
return TRACE_ENTITIES_ONLY;
}
};
class CTraceFilterPlayersNPCsPhysicsProps : public ITraceFilter
{
public:
bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
if ( pEntity )
{
if ( pEntity->IsPlayer() || pEntity->MyNPCPointer() || pEntity->m_iClassname == g_iszPhysicsPropClassname )
return true;
}
return false;
}
virtual TraceType_t GetTraceType() const
{
return TRACE_ENTITIES_ONLY;
}
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CEnvBeam::PassesTouchFilters(CBaseEntity *pOther)
{
bool fPassedSoFar = false;
// Touched some player or NPC!
if( m_TouchType != touch_npc_only )
{
if( pOther->IsPlayer() )
{
fPassedSoFar = true;
}
}
if( m_TouchType != touch_player_only )
{
if( pOther->IsNPC() )
{
fPassedSoFar = true;
}
}
if( m_TouchType == touch_player_or_npc_or_physicsprop )
{
if( pOther->m_iClassname == g_iszPhysicsPropClassname )
{
fPassedSoFar = true;
}
}
if( fPassedSoFar )
{
CBaseFilter* pFilter = (CBaseFilter*)(m_hFilter.Get());
return (!pFilter) ? true : pFilter->PassesFilter( this, pOther );
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvBeam::UpdateThink( void )
{
// Apply damage every 1/10th of a second.
if ( ( m_flDamage > 0 ) && ( gpGlobals->curtime >= m_flFireTime + 0.1 ) )
{
trace_t tr;
UTIL_TraceLine( GetAbsStartPos(), GetAbsEndPos(), MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr );
BeamDamage( &tr );
// BeamDamage calls RelinkBeam, so no need to call it again.
}
else
{
RelinkBeam();
}
if( m_TouchType != touch_none )
{
trace_t tr;
Ray_t ray;
ray.Init( GetAbsStartPos(), GetAbsEndPos() );
if( m_TouchType == touch_player_or_npc_or_physicsprop )
{
CTraceFilterPlayersNPCsPhysicsProps traceFilter;
enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr );
}
else
{
CTraceFilterPlayersNPCs traceFilter;
enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr );
}
if( tr.fraction != 1.0 && PassesTouchFilters( tr.m_pEnt ) )
{
m_OnTouchedByEntity.FireOutput( tr.m_pEnt, this, 0 );
return;
}
}
SetNextThink( gpGlobals->curtime );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &vecSrc -
// &vecDest -
//-----------------------------------------------------------------------------
void CEnvBeam::Zap( const Vector &vecSrc, const Vector &vecDest )
{
CBroadcastRecipientFilter filter;
te->BeamPoints( filter, 0.0,
&vecSrc,
&vecDest,
m_spriteTexture,
0, // No halo
m_frameStart,
(int)m_flFrameRate,
m_life,
m_boltWidth,
m_boltWidth, // End width
0, // No fade
m_noiseAmplitude,
m_clrRender->r,
m_clrRender->g,
m_clrRender->b,
m_clrRender->a,
m_speed );
DoSparks( vecSrc, vecDest );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvBeam::RandomArea( void )
{
int iLoops = 0;
for (iLoops = 0; iLoops < 10; iLoops++)
{
Vector vecSrc = GetAbsOrigin();
Vector vecDir1 = Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ),random->RandomFloat( -1.0, 1.0 ) );
VectorNormalize( vecDir1 );
trace_t tr1;
UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr1 );
if (tr1.fraction == 1.0)
continue;
Vector vecDir2;
do {
vecDir2 = Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ),random->RandomFloat( -1.0, 1.0 ) );
} while (DotProduct(vecDir1, vecDir2 ) > 0);
VectorNormalize( vecDir2 );
trace_t tr2;
UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr2 );
if (tr2.fraction == 1.0)
continue;
if ((tr1.endpos - tr2.endpos).Length() < m_radius * 0.1)
continue;
UTIL_TraceLine( tr1.endpos, tr2.endpos, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr2 );
if (tr2.fraction != 1.0)
continue;
Zap( tr1.endpos, tr2.endpos );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : vecSrc -
//-----------------------------------------------------------------------------
void CEnvBeam::RandomPoint( const Vector &vecSrc )
{
int iLoops = 0;
for (iLoops = 0; iLoops < 10; iLoops++)
{
Vector vecDir1 = Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ),random->RandomFloat( -1.0, 1.0 ) );
VectorNormalize( vecDir1 );
trace_t tr1;
UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr1 );
if ((tr1.endpos - vecSrc).Length() < m_radius * 0.1)
continue;
if (tr1.fraction == 1.0)
continue;
Zap( vecSrc, tr1.endpos );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvBeam::BeamUpdateVars( void )
{
CBaseEntity *pStart = gEntList.FindEntityByName( NULL, m_iszStartEntity );
CBaseEntity *pEnd = gEntList.FindEntityByName( NULL, m_iszEndEntity );
// if the end entity is missing, we use the Hammer-specified vector offset instead.
bool bEndPointFromEntity = pEnd != NULL;
if (( pStart == NULL ) || ( !bEndPointFromEntity && !HasEndPointHandle() ))
{
return;
}
Vector vEndPointPos;
if ( bEndPointFromEntity )
{
vEndPointPos = pEnd->GetAbsOrigin();
}
else
{
EntityToWorldSpace( m_vEndPointRelative, &vEndPointPos );
}
m_nNumBeamEnts = 2;
m_speed = clamp( m_speed, 0, MAX_BEAM_SCROLLSPEED );
// NOTE: If the end entity is the beam itself (and the start entity
// isn't *also* the beam itself, we've got problems. This is a problem
// because SetAbsStartPos actually sets the entity's origin.
if ( ( pEnd == this ) && ( pStart != this ) )
{
DevMsg("env_beams cannot have the end entity be the beam itself\n"
"unless the start entity is also the beam itself!\n" );
Assert(0);
}
SetModelName( m_iszSpriteName );
SetTexture( m_spriteTexture );
SetType( BEAM_ENTPOINT );
if ( IsStaticPointEntity( pStart ) )
{
SetAbsStartPos( pStart->GetAbsOrigin() );
}
else
{
SetStartEntity( pStart );
}
if ( !bEndPointFromEntity || IsStaticPointEntity( pEnd ) )
{
SetAbsEndPos( vEndPointPos );
}
else
{
SetEndEntity( pEnd );
}
RelinkBeam();
SetWidth( MIN(MAX_BEAM_WIDTH, m_boltWidth) );
SetNoise( MIN(MAX_BEAM_NOISEAMPLITUDE, m_noiseAmplitude) );
SetFrame( m_frameStart );
SetScrollRate( m_speed );
if ( m_spawnflags & SF_BEAM_SHADEIN )
{
SetBeamFlags( FBEAM_SHADEIN );
}
else if ( m_spawnflags & SF_BEAM_SHADEOUT )
{
SetBeamFlags( FBEAM_SHADEOUT );
}
}
+300
View File
@@ -0,0 +1,300 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements visual effects entities: sprites, beams, bubbles, etc.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "shake.h"
#ifdef INFESTED_DLL
#include "asw_marine.h"
#include "asw_player.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CEnvFade : public CLogicalEntity
{
private:
float m_Duration;
float m_HoldTime;
COutputEvent m_OnBeginFade;
DECLARE_DATADESC();
float m_flFadeStartTime;
float m_flReverseFadeStartTime;
float m_flReverseFadeDuration;
public:
DECLARE_CLASS( CEnvFade, CLogicalEntity );
CEnvFade();
virtual void Spawn( void );
inline float Duration( void ) { return m_Duration; }
inline float HoldTime( void ) { return m_HoldTime; }
inline void SetDuration( float duration ) { m_Duration = duration; }
inline void SetHoldTime( float hold ) { m_HoldTime = hold; }
int DrawDebugTextOverlays(void);
// Inputs
void InputFade( inputdata_t &inputdata );
void InputReverseFade( inputdata_t &inputdata );
};
LINK_ENTITY_TO_CLASS( env_fade, CEnvFade );
BEGIN_DATADESC( CEnvFade )
DEFINE_KEYFIELD( m_Duration, FIELD_FLOAT, "duration" ),
DEFINE_KEYFIELD( m_HoldTime, FIELD_FLOAT, "holdtime" ),
DEFINE_KEYFIELD( m_flReverseFadeDuration, FIELD_FLOAT, "ReverseFadeDuration" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Fade", InputFade ),
DEFINE_INPUTFUNC( FIELD_VOID, "FadeReverse", InputReverseFade ),
DEFINE_OUTPUT( m_OnBeginFade, "OnBeginFade"),
END_DATADESC()
#define SF_FADE_IN 0x0001 // Fade in, not out
#define SF_FADE_MODULATE 0x0002 // Modulate, don't blend
#define SF_FADE_ONLYONE 0x0004
#define SF_FADE_STAYOUT 0x0008
CEnvFade::CEnvFade()
: m_flFadeStartTime(0.0f),
m_flReverseFadeStartTime(0.0f)
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvFade::Spawn( void )
{
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that does the screen fade.
//-----------------------------------------------------------------------------
void CEnvFade::InputFade( inputdata_t &inputdata )
{
int fadeFlags = 0;
if ( m_spawnflags & SF_FADE_IN )
{
fadeFlags |= FFADE_IN;
}
else
{
fadeFlags |= FFADE_OUT;
}
if ( m_spawnflags & SF_FADE_MODULATE )
{
fadeFlags |= FFADE_MODULATE;
}
if ( m_spawnflags & SF_FADE_STAYOUT )
{
fadeFlags |= FFADE_STAYOUT;
}
//color32 fadeColor = m_clrRender;
//if( m_flReverseFadeStartTime != 0.0f )
//{
// float flCurrentFadeTime = gpGlobals->curtime - m_flReverseFadeStartTime;
// //Change the fade alpha to match the alpha of the current fade to prevent a pop
// if( flCurrentFadeTime < m_Duration )
// {
// fadeColor.a = m_clrRender.GetA() * ( flCurrentFadeTime )/ m_flReverseFadeDuration;
// }
//}
if ( m_spawnflags & SF_FADE_ONLYONE )
{
#ifdef INFESTED_DLL
if ( inputdata.pActivator->Classify() == CLASS_ASW_MARINE )
{
CASW_Marine *pMarine = static_cast<CASW_Marine*>( inputdata.pActivator );
CASW_Player *pPlayer = pMarine->GetCommander();
if ( pPlayer && pMarine->IsInhabited() )
{
UTIL_ScreenFade( pPlayer, m_clrRender, Duration(), HoldTime(), fadeFlags );
}
}
#else
if ( inputdata.pActivator->IsNetClient() )
{
UTIL_ScreenFade( inputdata.pActivator, m_clrRender, Duration(), HoldTime(), fadeFlags );
}
#endif
}
else
{
UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), fadeFlags|FFADE_PURGE );
}
m_flFadeStartTime = gpGlobals->curtime;
m_OnBeginFade.FireOutput( inputdata.pActivator, this );
}
void CEnvFade::InputReverseFade( inputdata_t &inputdata )
{
int fadeFlags = 0;
if ( m_spawnflags & SF_FADE_IN )
{
fadeFlags |= FFADE_OUT;
}
else
{
fadeFlags |= FFADE_IN;
}
if ( m_spawnflags & SF_FADE_MODULATE )
{
fadeFlags |= FFADE_MODULATE;
}
if ( m_spawnflags & SF_FADE_STAYOUT )
{
fadeFlags |= FFADE_STAYOUT;
}
color32 fadeColor = m_clrRender;
if( m_flFadeStartTime != 0.0f )
{
float flCurrentFadeTime = gpGlobals->curtime - m_flFadeStartTime;
//Change the fade alpha to match the alpha of the current fade to prevent a pop
if( flCurrentFadeTime < m_Duration )
{
fadeColor.a = m_clrRender.GetA() * ( flCurrentFadeTime )/ m_Duration;
}
}
if ( m_spawnflags & SF_FADE_ONLYONE )
{
if ( inputdata.pActivator->IsNetClient() )
{
UTIL_ScreenFade( inputdata.pActivator, fadeColor, m_flReverseFadeDuration, HoldTime(), fadeFlags );
}
}
else
{
UTIL_ScreenFadeAll( fadeColor, m_flReverseFadeDuration, HoldTime(), fadeFlags|FFADE_PURGE );
}
m_flReverseFadeStartTime = gpGlobals->curtime;
m_OnBeginFade.FireOutput( inputdata.pActivator, this );
}
//-----------------------------------------------------------------------------
// Purpose: Fetches the arguments from the command line for the fadein and fadeout
// console commands.
// Input : flTime - Returns the fade time in seconds (the time to fade in or out)
// clrFade - Returns the color to fade to or from.
//-----------------------------------------------------------------------------
static void GetFadeParms( const CCommand &args, float &flTime, color32 &clrFade)
{
flTime = 2.0f;
if ( args.ArgC() > 1 )
{
flTime = atof( args[1] );
}
clrFade.r = 0;
clrFade.g = 0;
clrFade.b = 0;
clrFade.a = 255;
if ( args.ArgC() > 4 )
{
clrFade.r = atoi( args[2] );
clrFade.g = atoi( args[3] );
clrFade.b = atoi( args[4] );
if ( args.ArgC() == 5 )
{
clrFade.a = atoi( args[5] );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Console command to fade out to a given color.
//-----------------------------------------------------------------------------
static void CC_FadeOut( const CCommand &args )
{
float flTime;
color32 clrFade;
GetFadeParms( args, flTime, clrFade );
CBasePlayer *pPlayer = UTIL_GetCommandClient();
UTIL_ScreenFade( pPlayer, clrFade, flTime, 0, FFADE_OUT | FFADE_PURGE | FFADE_STAYOUT );
}
static ConCommand fadeout("fadeout", CC_FadeOut, "fadeout {time r g b}: Fades the screen to black or to the specified color over the given number of seconds.", FCVAR_CHEAT );
//-----------------------------------------------------------------------------
// Purpose: Console command to fade in from a given color.
//-----------------------------------------------------------------------------
static void CC_FadeIn( const CCommand &args )
{
float flTime;
color32 clrFade;
GetFadeParms( args, flTime, clrFade );
CBasePlayer *pPlayer = UTIL_GetCommandClient();
UTIL_ScreenFade( pPlayer, clrFade, flTime, 0, FFADE_IN | FFADE_PURGE );
}
static ConCommand fadein("fadein", CC_FadeIn, "fadein {time r g b}: Fades the screen in from black or from the specified color over the given number of seconds.", FCVAR_CHEAT );
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CEnvFade::DrawDebugTextOverlays( void )
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
char tempstr[512];
// print duration
Q_snprintf(tempstr,sizeof(tempstr)," duration: %f", m_Duration);
EntityText(text_offset,tempstr,0);
text_offset++;
// print hold time
Q_snprintf(tempstr,sizeof(tempstr)," hold time: %f", m_HoldTime);
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
+124
View File
@@ -0,0 +1,124 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements visual effects entities: sprites, beams, bubbles, etc.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "engine/IEngineSound.h"
#include "baseentity.h"
#include "entityoutput.h"
#include "recipientfilter.h"
#include "usermessages.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CEnvHudHint : public CPointEntity
{
public:
DECLARE_CLASS( CEnvHudHint, CPointEntity );
void Spawn( void );
void Precache( void );
private:
void InputShowHudHint( inputdata_t &inputdata );
void InputHideHudHint( inputdata_t &inputdata );
string_t m_iszMessage;
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( env_hudhint, CEnvHudHint );
BEGIN_DATADESC( CEnvHudHint )
DEFINE_KEYFIELD( m_iszMessage, FIELD_STRING, "message" ),
DEFINE_INPUTFUNC( FIELD_VOID, "ShowHudHint", InputShowHudHint ),
DEFINE_INPUTFUNC( FIELD_VOID, "HideHudHint", InputHideHudHint ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvHudHint::Spawn( void )
{
Precache();
SetSolid( SOLID_NONE );
SetMoveType( MOVETYPE_NONE );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvHudHint::Precache( void )
{
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for showing the message and/or playing the sound.
//-----------------------------------------------------------------------------
void CEnvHudHint::InputShowHudHint( inputdata_t &inputdata )
{
CBaseEntity *pPlayer = NULL;
if ( inputdata.pActivator && inputdata.pActivator->IsPlayer() )
{
pPlayer = inputdata.pActivator;
}
else
{
pPlayer = UTIL_GetLocalPlayer();
}
if ( pPlayer )
{
if ( !pPlayer || !pPlayer->IsNetClient() )
return;
CSingleUserRecipientFilter user( (CBasePlayer *)pPlayer );
user.MakeReliable();
CCSUsrMsg_KeyHintText msg;
msg.add_hints( STRING(m_iszMessage) );
SendUserMessage( user, CS_UM_KeyHintText, msg );
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CEnvHudHint::InputHideHudHint( inputdata_t &inputdata )
{
CBaseEntity *pPlayer = NULL;
if ( inputdata.pActivator && inputdata.pActivator->IsPlayer() )
{
pPlayer = inputdata.pActivator;
}
else
{
pPlayer = UTIL_GetLocalPlayer();
}
if ( pPlayer )
{
if ( !pPlayer || !pPlayer->IsNetClient() )
return;
CSingleUserRecipientFilter user( (CBasePlayer *)pPlayer );
user.MakeReliable();
CCSUsrMsg_KeyHintText msg;
msg.add_hints( STRING(NULL_STRING) );
SendUserMessage( user, CS_UM_KeyHintText, msg );
}
}
+250
View File
@@ -0,0 +1,250 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: A special kind of beam effect that traces from its start position to
// its end position and stops if it hits anything.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "EnvLaser.h"
#include "Sprite.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_ENTITY_TO_CLASS( env_laser, CEnvLaser );
BEGIN_DATADESC( CEnvLaser )
DEFINE_KEYFIELD( m_iszLaserTarget, FIELD_STRING, "LaserTarget" ),
DEFINE_FIELD( m_pSprite, FIELD_CLASSPTR ),
DEFINE_KEYFIELD( m_iszSpriteName, FIELD_STRING, "EndSprite" ),
DEFINE_FIELD( m_firePosition, FIELD_VECTOR ),
DEFINE_KEYFIELD( m_flStartFrame, FIELD_FLOAT, "framestart" ),
// Function Pointers
DEFINE_FUNCTION( StrikeThink ),
// Input functions
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::Spawn( void )
{
if ( !GetModelName() )
{
SetThink( &CEnvLaser::SUB_Remove );
return;
}
SetSolid( SOLID_NONE ); // Remove model & collisions
SetThink( &CEnvLaser::StrikeThink );
SetEndWidth( GetWidth() ); // Note: EndWidth is not scaled
PointsInit( GetLocalOrigin(), GetLocalOrigin() );
Precache( );
if ( !m_pSprite && m_iszSpriteName != NULL_STRING )
{
m_pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteName), GetAbsOrigin(), TRUE );
}
else
{
m_pSprite = NULL;
}
if ( m_pSprite )
{
m_pSprite->SetParent( GetMoveParent() );
m_pSprite->SetTransparency( kRenderGlow, m_clrRender->r, m_clrRender->g, m_clrRender->b, m_clrRender->a, m_nRenderFX );
}
if ( GetEntityName() != NULL_STRING && !(m_spawnflags & SF_BEAM_STARTON) )
{
TurnOff();
}
else
{
TurnOn();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::Precache( void )
{
SetModelIndex( PrecacheModel( STRING( GetModelName() ) ) );
if ( m_iszSpriteName != NULL_STRING )
PrecacheModel( STRING(m_iszSpriteName) );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CEnvLaser::KeyValue( const char *szKeyName, const char *szValue )
{
if (FStrEq(szKeyName, "width"))
{
SetWidth( atof(szValue) );
}
else if (FStrEq(szKeyName, "NoiseAmplitude"))
{
SetNoise( atoi(szValue) );
}
else if (FStrEq(szKeyName, "TextureScroll"))
{
SetScrollRate( atoi(szValue) );
}
else if (FStrEq(szKeyName, "texture"))
{
SetModelName( AllocPooledString(szValue) );
}
else
{
BaseClass::KeyValue( szKeyName, szValue );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether the laser is currently active.
//-----------------------------------------------------------------------------
int CEnvLaser::IsOn( void )
{
if ( IsEffectActive( EF_NODRAW ) )
return 0;
return 1;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::InputTurnOn( inputdata_t &inputdata )
{
if (!IsOn())
{
TurnOn();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::InputTurnOff( inputdata_t &inputdata )
{
if (IsOn())
{
TurnOff();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::InputToggle( inputdata_t &inputdata )
{
if ( IsOn() )
{
TurnOff();
}
else
{
TurnOn();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::TurnOff( void )
{
AddEffects( EF_NODRAW );
if ( m_pSprite )
m_pSprite->TurnOff();
SetNextThink( TICK_NEVER_THINK );
SetThink( NULL );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::TurnOn( void )
{
RemoveEffects( EF_NODRAW );
if ( m_pSprite )
m_pSprite->TurnOn();
m_flFireTime = gpGlobals->curtime;
SetThink( &CEnvLaser::StrikeThink );
//
// Call StrikeThink here to update the end position, otherwise we will see
// the beam in the wrong place for one frame since we cleared the nodraw flag.
//
StrikeThink();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::FireAtPoint( trace_t &tr )
{
SetAbsEndPos( tr.endpos );
if ( m_pSprite )
{
UTIL_SetOrigin( m_pSprite, tr.endpos );
}
// Apply damage and do sparks every 1/10th of a second.
if ( gpGlobals->curtime >= m_flFireTime + 0.1 )
{
BeamDamage( &tr );
DoSparks( GetAbsStartPos(), tr.endpos );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvLaser::StrikeThink( void )
{
CBaseEntity *pEnd = RandomTargetname( STRING( m_iszLaserTarget ) );
Vector vecFireAt = GetAbsEndPos();
if ( pEnd )
{
vecFireAt = pEnd->GetAbsOrigin();
}
trace_t tr;
UTIL_TraceLine( GetAbsOrigin(), vecFireAt, MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr );
FireAtPoint( tr );
SetNextThink( gpGlobals->curtime );
}
+51
View File
@@ -0,0 +1,51 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef ENVLASER_H
#define ENVLASER_H
#ifdef _WIN32
#pragma once
#endif
#include "baseentity.h"
#include "beam_shared.h"
#include "entityoutput.h"
class CSprite;
class CEnvLaser : public CBeam
{
DECLARE_CLASS( CEnvLaser, CBeam );
public:
void Spawn( void );
void Precache( void );
bool KeyValue( const char *szKeyName, const char *szValue );
void TurnOn( void );
void TurnOff( void );
int IsOn( void );
void FireAtPoint( trace_t &point );
void StrikeThink( void );
void InputTurnOn( inputdata_t &inputdata );
void InputTurnOff( inputdata_t &inputdata );
void InputToggle( inputdata_t &inputdata );
DECLARE_DATADESC();
string_t m_iszLaserTarget; // Name of entity or entities to strike at, randomly picked if more than one match.
CSprite *m_pSprite;
string_t m_iszSpriteName;
Vector m_firePosition;
float m_flStartFrame;
};
#endif // ENVLASER_H
+415
View File
@@ -0,0 +1,415 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements visual effects entities: sprites, beams, bubbles, etc.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "EnvMessage.h"
#include "engine/IEngineSound.h"
#include "keyvalues.h"
#include "filesystem.h"
#include "color.h"
#include "GameStats.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
LINK_ENTITY_TO_CLASS( env_message, CMessage );
BEGIN_DATADESC( CMessage )
DEFINE_KEYFIELD( m_iszMessage, FIELD_STRING, "message" ),
DEFINE_KEYFIELD( m_sNoise, FIELD_SOUNDNAME, "messagesound" ),
DEFINE_KEYFIELD( m_MessageAttenuation, FIELD_INTEGER, "messageattenuation" ),
DEFINE_KEYFIELD( m_MessageVolume, FIELD_FLOAT, "messagevolume" ),
DEFINE_FIELD( m_Radius, FIELD_FLOAT ),
DEFINE_INPUTFUNC( FIELD_VOID, "ShowMessage", InputShowMessage ),
DEFINE_OUTPUT(m_OnShowMessage, "OnShowMessage"),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMessage::Spawn( void )
{
Precache();
SetSolid( SOLID_NONE );
SetMoveType( MOVETYPE_NONE );
switch( m_MessageAttenuation )
{
case 1: // Medium radius
m_Radius = ATTN_STATIC;
break;
case 2: // Large radius
m_Radius = ATTN_NORM;
break;
case 3: //EVERYWHERE
m_Radius = ATTN_NONE;
break;
default:
case 0: // Small radius
m_Radius = SNDLVL_IDLE;
break;
}
m_MessageAttenuation = 0;
// Remap volume from [0,10] to [0,1].
m_MessageVolume *= 0.1;
// No volume, use normal
if ( m_MessageVolume <= 0 )
{
m_MessageVolume = 1.0;
}
}
void CMessage::Precache( void )
{
if ( m_sNoise != NULL_STRING )
{
PrecacheScriptSound( STRING(m_sNoise) );
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for showing the message and/or playing the sound.
//-----------------------------------------------------------------------------
void CMessage::InputShowMessage( inputdata_t &inputdata )
{
CBaseEntity *pPlayer = NULL;
if ( m_spawnflags & SF_MESSAGE_ALL )
{
#ifdef CSTRIKE15
UTIL_ClientPrintAll( HUD_PRINTCENTER, STRING( m_iszMessage ) );
#else
UTIL_ShowMessageAll( STRING( m_iszMessage ) );
#endif
}
else
{
if ( inputdata.pActivator && inputdata.pActivator->IsPlayer() )
{
pPlayer = inputdata.pActivator;
}
else
{
pPlayer = (gpGlobals->maxClients > 1) ? NULL : UTIL_GetLocalPlayer();
}
if ( pPlayer && pPlayer->IsPlayer() )
{
#ifdef CSTRIKE15
CRecipientFilter filterPlayer;
filterPlayer.MakeReliable();
filterPlayer.AddRecipient( ToBasePlayer( pPlayer ) );
UTIL_ClientPrintFilter( filterPlayer, HUD_PRINTCENTER, STRING( m_iszMessage ) );
filterPlayer.RemoveAllRecipients();
#else
UTIL_ShowMessage( STRING( m_iszMessage ), ToBasePlayer( pPlayer ) );
#endif
}
}
if ( m_sNoise != NULL_STRING )
{
CPASAttenuationFilter filter( this );
EmitSound_t ep;
ep.m_nChannel = CHAN_BODY;
ep.m_pSoundName = (char*)STRING(m_sNoise);
ep.m_flVolume = m_MessageVolume;
ep.m_SoundLevel = ATTN_TO_SNDLVL( m_Radius );
EmitSound( filter, entindex(), ep );
}
if ( m_spawnflags & SF_MESSAGE_ONCE )
{
UTIL_Remove( this );
}
m_OnShowMessage.FireOutput( inputdata.pActivator, this );
}
void CMessage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
inputdata_t inputdata;
inputdata.pActivator = NULL;
inputdata.pCaller = NULL;
InputShowMessage( inputdata );
}
class CCredits : public CPointEntity
{
public:
DECLARE_CLASS( CMessage, CPointEntity );
DECLARE_DATADESC();
void Spawn( void );
void InputRollCredits( inputdata_t &inputdata );
void InputRollOutroCredits( inputdata_t &inputdata );
void InputShowLogo( inputdata_t &inputdata );
void InputSetLogoLength( inputdata_t &inputdata );
COutputEvent m_OnCreditsDone;
virtual void OnRestore();
private:
void RollOutroCredits();
bool m_bRolledOutroCredits;
float m_flLogoLength;
};
LINK_ENTITY_TO_CLASS( env_credits, CCredits );
BEGIN_DATADESC( CCredits )
DEFINE_INPUTFUNC( FIELD_VOID, "RollCredits", InputRollCredits ),
DEFINE_INPUTFUNC( FIELD_VOID, "RollOutroCredits", InputRollOutroCredits ),
DEFINE_INPUTFUNC( FIELD_VOID, "ShowLogo", InputShowLogo ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetLogoLength", InputSetLogoLength ),
DEFINE_OUTPUT( m_OnCreditsDone, "OnCreditsDone"),
DEFINE_FIELD( m_bRolledOutroCredits, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flLogoLength, FIELD_FLOAT )
END_DATADESC()
void CCredits::Spawn( void )
{
SetSolid( SOLID_NONE );
SetMoveType( MOVETYPE_NONE );
}
#ifndef PORTAL
static void CreditsDone_f( void )
{
CCredits *pCredits = (CCredits*)gEntList.FindEntityByClassname( NULL, "env_credits" );
if ( pCredits )
{
pCredits->m_OnCreditsDone.FireOutput( pCredits, pCredits );
}
}
static ConCommand creditsdone("creditsdone", CreditsDone_f );
#endif // PORTAL
extern ConVar sv_unlockedchapters;
void CCredits::OnRestore()
{
BaseClass::OnRestore();
if ( m_bRolledOutroCredits )
{
// Roll them again so that the client .dll will send the "creditsdone" message and we'll
// actually get back to the main menu
RollOutroCredits();
}
}
void CCredits::RollOutroCredits()
{
sv_unlockedchapters.SetValue( "15" );
// Gurjeets - Not used in CSGO
//CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
//CSingleUserRecipientFilter user( pPlayer );
//user.MakeReliable();
//UserMessageBegin( user, "CreditsMsg" );
// WRITE_BYTE( 3 );
//MessageEnd();
}
void CCredits::InputRollOutroCredits( inputdata_t &inputdata )
{
RollOutroCredits();
// In case we save restore
m_bRolledOutroCredits = true;
#ifndef _GAMECONSOLE
gamestats->Event_Credits();
#endif
}
void CCredits::InputShowLogo( inputdata_t &inputdata )
{
ASSERT(0);
// Gurjeets - Not used in CSGO
//CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
//CSingleUserRecipientFilter user( pPlayer );
//user.MakeReliable();
//if ( m_flLogoLength )
//{
// UserMessageBegin( user, "LogoTimeMsg" );
// WRITE_FLOAT( m_flLogoLength );
// MessageEnd();
//}
//else
//{
// UserMessageBegin( user, "CreditsMsg" );
// WRITE_BYTE( 1 );
// MessageEnd();
//}
}
void CCredits::InputSetLogoLength( inputdata_t &inputdata )
{
m_flLogoLength = inputdata.value.Float();
}
void CCredits::InputRollCredits( inputdata_t &inputdata )
{
// Gurjeets - Not used in CSGO
//CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
//CSingleUserRecipientFilter user( pPlayer );
//user.MakeReliable();
//UserMessageBegin( user, "CreditsMsg" );
// WRITE_BYTE( 2 );
//MessageEnd();
}
//-----------------------------------------------------------------------------
// Purpose: Play the outtro stats at the end of the campaign
//-----------------------------------------------------------------------------
class COuttroStats : public CPointEntity
{
public:
DECLARE_CLASS( COuttroStats, CPointEntity );
DECLARE_DATADESC();
void Spawn( void );
void InputRollCredits( inputdata_t &inputdata );
void InputRollStatsCrawl( inputdata_t &inputdata );
void InputSkipStateChanged( inputdata_t &inputdata );
void SkipThink( void );
void CalcSkipState( int &skippingPlayers, int &totalPlayers );
COutputEvent m_OnOuttroStatsDone;
};
//-----------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( env_outtro_stats, COuttroStats );
BEGIN_DATADESC( COuttroStats )
DEFINE_INPUTFUNC( FIELD_VOID, "RollStatsCrawl", InputRollStatsCrawl ),
DEFINE_INPUTFUNC( FIELD_VOID, "RollCredits", InputRollCredits ),
DEFINE_INPUTFUNC( FIELD_VOID, "SkipStateChanged", InputSkipStateChanged ),
DEFINE_OUTPUT( m_OnOuttroStatsDone, "OnOuttroStatsDone"),
END_DATADESC()
//-----------------------------------------------------------------------------
void COuttroStats::Spawn( void )
{
SetSolid( SOLID_NONE );
SetMoveType( MOVETYPE_NONE );
}
//-----------------------------------------------------------------------------
void COuttroStats::InputRollStatsCrawl( inputdata_t &inputdata )
{
// Gurjeets - Not used in CSGO
//CReliableBroadcastRecipientFilter players;
//UserMessageBegin( players, "StatsCrawlMsg" );
//MessageEnd();
SetThink( &COuttroStats::SkipThink );
SetNextThink( gpGlobals->curtime + 1.0 );
}
//-----------------------------------------------------------------------------
void COuttroStats::InputRollCredits( inputdata_t &inputdata )
{
ASSERT(0);
// Gurjeets - Not used in CSGO
//CReliableBroadcastRecipientFilter players;
//UserMessageBegin( players, "creditsMsg" );
//MessageEnd();
}
void COuttroStats::SkipThink( void )
{
// if all valid players are skipping, then end
int iNumSkips = 0;
int iNumPlayers = 0;
CalcSkipState( iNumSkips, iNumPlayers );
if ( iNumSkips >= iNumPlayers )
{
// TheDirector->StartScenarioExit();
}
else
SetNextThink( gpGlobals->curtime + 1.0 );
}
void COuttroStats::CalcSkipState( int &skippingPlayers, int &totalPlayers )
{
// calc skip state
skippingPlayers = 0;
totalPlayers = 0;
}
void COuttroStats::InputSkipStateChanged( inputdata_t &inputdata )
{
int iNumSkips = 0;
int iNumPlayers = 0;
CalcSkipState( iNumSkips, iNumPlayers );
DevMsg( "COuttroStats: Skip state changed. %d players, %d skips\n", iNumPlayers, iNumSkips );
// Gurjeets - Not used in CSGO
//// Don't send to players in singleplayer
//if ( iNumPlayers > 1 )
//{
// CReliableBroadcastRecipientFilter players;
// UserMessageBegin( players, "StatsSkipState" );
// WRITE_BYTE( iNumSkips );
// WRITE_BYTE( iNumPlayers );
// MessageEnd();
//}
}
void CC_Test_Outtro_Stats( const CCommand& args )
{
CBaseEntity *pOuttro = gEntList.FindEntityByClassname( NULL, "env_outtro_stats" );
if ( pOuttro )
{
variant_t emptyVariant;
pOuttro->AcceptInput( "RollStatsCrawl", NULL, NULL, emptyVariant, 0 );
}
}
static ConCommand test_outtro_stats("test_outtro_stats", CC_Test_Outtro_Stats, 0, FCVAR_CHEAT);
+48
View File
@@ -0,0 +1,48 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef ENVMESSAGE_H
#define ENVMESSAGE_H
#ifdef _WIN32
#pragma once
#endif
#include "baseentity.h"
#include "entityoutput.h"
#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out
#define SF_MESSAGE_ALL 0x0002 // Send to all clients
class CMessage : public CPointEntity
{
public:
DECLARE_CLASS( CMessage, CPointEntity );
void Spawn( void );
void Precache( void );
inline void SetMessage( string_t iszMessage ) { m_iszMessage = iszMessage; }
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
private:
void InputShowMessage( inputdata_t &inputdata );
string_t m_iszMessage; // Message to display.
float m_MessageVolume;
int m_MessageAttenuation;
float m_Radius;
DECLARE_DATADESC();
string_t m_sNoise;
COutputEvent m_OnShowMessage;
};
#endif // ENVMESSAGE_H
+581
View File
@@ -0,0 +1,581 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements a screen shake effect that can also shake physics objects.
//
// NOTE: UTIL_ScreenShake() will only shake players who are on the ground
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "shake.h"
#include "physics_saverestore.h"
#include "rope.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
class CPhysicsShake : public IMotionEvent
{
DECLARE_SIMPLE_DATADESC();
public:
virtual simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
Vector contact;
if ( !pObject->GetContactPoint( &contact, NULL ) )
return SIM_NOTHING;
// fudge the force a bit to make it more dramatic
pObject->CalculateForceOffset( m_force * (1.0f + pObject->GetMass()*0.4f), contact, &linear, &angular );
return SIM_LOCAL_FORCE;
}
Vector m_force;
};
BEGIN_SIMPLE_DATADESC( CPhysicsShake )
DEFINE_FIELD( m_force, FIELD_VECTOR ),
END_DATADESC()
class CEnvShake : public CPointEntity
{
private:
float m_Amplitude;
float m_Frequency;
float m_Duration;
float m_Radius; // radius of 0 means all players
float m_stopTime;
float m_nextShake;
float m_currentAmp;
Vector m_maxForce;
IPhysicsMotionController *m_pShakeController;
CPhysicsShake m_shakeCallback;
DECLARE_DATADESC();
public:
DECLARE_CLASS( CEnvShake, CPointEntity );
virtual ~CEnvShake( void );
virtual void Precache( void );
virtual void Spawn( void );
virtual void OnRestore( void );
inline float Amplitude( void ) { return m_Amplitude; }
inline float Frequency( void ) { return m_Frequency; }
inline float Duration( void ) { return m_Duration; }
float Radius( bool bPlayers = true );
inline void SetAmplitude( float amplitude ) { m_Amplitude = amplitude; }
inline void SetFrequency( float frequency ) { m_Frequency = frequency; }
inline void SetDuration( float duration ) { m_Duration = duration; }
inline void SetRadius( float radius ) { m_Radius = radius; }
int DrawDebugTextOverlays(void);
// Input handlers
void InputStartShake( inputdata_t &inputdata );
void InputStopShake( inputdata_t &inputdata );
void InputAmplitude( inputdata_t &inputdata );
void InputFrequency( inputdata_t &inputdata );
// Causes the camera/physics shakes to happen:
void ApplyShake( ShakeCommand_t command );
void Think( void );
};
LINK_ENTITY_TO_CLASS( env_shake, CEnvShake );
BEGIN_DATADESC( CEnvShake )
DEFINE_KEYFIELD( m_Amplitude, FIELD_FLOAT, "amplitude" ),
DEFINE_KEYFIELD( m_Frequency, FIELD_FLOAT, "frequency" ),
DEFINE_KEYFIELD( m_Duration, FIELD_FLOAT, "duration" ),
DEFINE_KEYFIELD( m_Radius, FIELD_FLOAT, "radius" ),
DEFINE_FIELD( m_stopTime, FIELD_TIME ),
DEFINE_FIELD( m_nextShake, FIELD_TIME ),
DEFINE_FIELD( m_currentAmp, FIELD_FLOAT ),
DEFINE_FIELD( m_maxForce, FIELD_VECTOR ),
DEFINE_PHYSPTR( m_pShakeController ),
DEFINE_EMBEDDED( m_shakeCallback ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartShake", InputStartShake ),
DEFINE_INPUTFUNC( FIELD_VOID, "StopShake", InputStopShake ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "Amplitude", InputAmplitude ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "Frequency", InputFrequency ),
END_DATADESC()
#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius
#define SF_SHAKE_INAIR 0x0004 // Shake players in air
#define SF_SHAKE_PHYSICS 0x0008 // Shake physically (not just camera)
#define SF_SHAKE_ROPES 0x0010 // Shake ropes too.
#define SF_SHAKE_NO_VIEW 0x0020 // DON'T shake the view (only ropes and/or physics objects)
#define SF_SHAKE_NO_RUMBLE 0x0040 // DON'T Rumble the XBox Controller
#define SF_TILT_EASE_INOUT 0x0080 // Ease in and out of the tilt
//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CEnvShake::~CEnvShake( void )
{
if ( m_pShakeController )
{
physenv->DestroyMotionController( m_pShakeController );
}
}
float CEnvShake::Radius(bool bPlayers)
{
// The radius for players is zero if SF_SHAKE_EVERYONE is set
if ( bPlayers && HasSpawnFlags(SF_SHAKE_EVERYONE))
return 0;
return m_Radius;
}
//-----------------------------------------------------------------------------
// Purpose: Sets default member values when spawning.
//-----------------------------------------------------------------------------
void CEnvShake::Precache()
{
BaseClass::Precache();
CRopeKeyframe::PrecacheShakeRopes();
}
void CEnvShake::Spawn( void )
{
Precache();
SetSolid( SOLID_NONE );
SetMoveType( MOVETYPE_NONE );
if ( GetSpawnFlags() & SF_SHAKE_EVERYONE )
{
m_Radius = 0;
}
if ( HasSpawnFlags( SF_SHAKE_NO_VIEW ) && !HasSpawnFlags( SF_SHAKE_PHYSICS ) && !HasSpawnFlags( SF_SHAKE_ROPES ) )
{
DevWarning( "env_shake %s with \"Don't shake view\" spawnflag set without \"Shake physics\" or \"Shake ropes\" spawnflags set.", GetDebugName() );
}
}
//-----------------------------------------------------------------------------
// Purpose: Restore the motion controller
//-----------------------------------------------------------------------------
void CEnvShake::OnRestore( void )
{
BaseClass::OnRestore();
if ( m_pShakeController )
{
m_pShakeController->SetEventHandler( &m_shakeCallback );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvShake::ApplyShake( ShakeCommand_t command )
{
if ( !HasSpawnFlags( SF_SHAKE_NO_VIEW ) || !HasSpawnFlags( SF_SHAKE_NO_RUMBLE ) )
{
bool air = (GetSpawnFlags() & SF_SHAKE_INAIR) ? true : false;
UTIL_ScreenShake( GetAbsOrigin(), Amplitude(), Frequency(), Duration(), Radius(), command, air );
}
if ( GetSpawnFlags() & SF_SHAKE_ROPES )
{
CRopeKeyframe::ShakeRopes( GetAbsOrigin(), Radius(false), Frequency() );
}
if ( GetSpawnFlags() & SF_SHAKE_PHYSICS )
{
if ( !m_pShakeController )
{
m_pShakeController = physenv->CreateMotionController( &m_shakeCallback );
}
// do physics shake
switch( command )
{
case SHAKE_START:
case SHAKE_START_NORUMBLE:
case SHAKE_START_RUMBLEONLY:
{
m_stopTime = gpGlobals->curtime + Duration();
m_nextShake = 0;
m_pShakeController->ClearObjects();
SetNextThink( gpGlobals->curtime );
m_currentAmp = Amplitude();
CBaseEntity *list[1024];
float radius = Radius(false);
// probably checked "Shake Everywhere" do a big radius
if ( !radius )
{
radius = 512;
}
Vector extents = Vector(radius, radius, radius);
extents.z = MAX(extents.z, 100);
Vector mins = GetAbsOrigin() - extents;
Vector maxs = GetAbsOrigin() + extents;
int count = UTIL_EntitiesInBox( list, 1024, mins, maxs, 0 );
for ( int i = 0; i < count; i++ )
{
//
// Only shake physics entities that players can see. This is one frame out of date
// so it's possible that we could miss objects if a player changed PVS this frame.
//
if ( ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS ) )
{
IPhysicsObject *pPhys = list[i]->VPhysicsGetObject();
if ( pPhys && pPhys->IsMoveable() )
{
m_pShakeController->AttachObject( pPhys, false );
pPhys->Wake();
}
}
}
}
break;
case SHAKE_STOP:
m_pShakeController->ClearObjects();
break;
case SHAKE_AMPLITUDE:
m_currentAmp = Amplitude();
case SHAKE_FREQUENCY:
m_pShakeController->WakeObjects();
break;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that starts the screen shake.
//-----------------------------------------------------------------------------
void CEnvShake::InputStartShake( inputdata_t &inputdata )
{
if ( HasSpawnFlags( SF_SHAKE_NO_RUMBLE ) )
{
ApplyShake( SHAKE_START_NORUMBLE );
}
else if ( HasSpawnFlags( SF_SHAKE_NO_VIEW ) )
{
ApplyShake( SHAKE_START_RUMBLEONLY );
}
else
{
ApplyShake( SHAKE_START );
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that stops the screen shake.
//-----------------------------------------------------------------------------
void CEnvShake::InputStopShake( inputdata_t &inputdata )
{
ApplyShake( SHAKE_STOP );
}
//-----------------------------------------------------------------------------
// Purpose: Handles changes to the shake amplitude from an external source.
//-----------------------------------------------------------------------------
void CEnvShake::InputAmplitude( inputdata_t &inputdata )
{
SetAmplitude( inputdata.value.Float() );
ApplyShake( SHAKE_AMPLITUDE );
}
//-----------------------------------------------------------------------------
// Purpose: Handles changes to the shake frequency from an external source.
//-----------------------------------------------------------------------------
void CEnvShake::InputFrequency( inputdata_t &inputdata )
{
SetFrequency( inputdata.value.Float() );
ApplyShake( SHAKE_FREQUENCY );
}
//-----------------------------------------------------------------------------
// Purpose: Calculates the physics shake values
//-----------------------------------------------------------------------------
void CEnvShake::Think( void )
{
int i;
if ( gpGlobals->curtime > m_nextShake )
{
// Higher frequency means we recalc the extents more often and perturb the display again
m_nextShake = gpGlobals->curtime + (1.0f / Frequency());
// Compute random shake extents (the shake will settle down from this)
for (i = 0; i < 2; i++ )
{
m_maxForce[i] = random->RandomFloat( -1, 1 );
}
// make the force it point mostly up
m_maxForce.z = 4;
VectorNormalize( m_maxForce );
m_maxForce *= m_currentAmp * 400; // amplitude is the acceleration of a 100kg object
}
float fraction = ( m_stopTime - gpGlobals->curtime ) / Duration();
if ( fraction < 0 )
{
m_pShakeController->ClearObjects();
return;
}
float freq = 0;
// Ramp up frequency over duration
if ( fraction )
{
freq = (Frequency() / fraction);
}
// square fraction to approach zero more quickly
fraction *= fraction;
// Sine wave that slowly settles to zero
fraction = fraction * sin( gpGlobals->curtime * freq );
// Add to view origin
for ( i = 0; i < 3; i++ )
{
// store the force in the controller callback
m_shakeCallback.m_force[i] = m_maxForce[i] * fraction;
}
// Drop amplitude a bit, less for higher frequency shakes
m_currentAmp -= m_currentAmp * ( gpGlobals->frametime / (Duration() * Frequency()) );
SetNextThink( gpGlobals->curtime );
}
//------------------------------------------------------------------------------
// Purpose: Console command to cause a screen shake.
//------------------------------------------------------------------------------
void CC_Shake( void )
{
CBasePlayer *pPlayer = UTIL_GetCommandClient();
if (pPlayer)
{
UTIL_ScreenShake( pPlayer->WorldSpaceCenter(), 25.0, 150.0, 1.0, 750, SHAKE_START );
}
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Returns current text offset from the top
//-----------------------------------------------------------------------------
int CEnvShake::DrawDebugTextOverlays( void )
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
char tempstr[512];
// print amplitude
Q_snprintf(tempstr,sizeof(tempstr)," magnitude: %f", m_Amplitude);
EntityText(text_offset,tempstr,0);
text_offset++;
// print frequency
Q_snprintf(tempstr,sizeof(tempstr)," frequency: %f", m_Frequency);
EntityText(text_offset,tempstr,0);
text_offset++;
// print duration
Q_snprintf(tempstr,sizeof(tempstr)," duration: %f", m_Duration);
EntityText(text_offset,tempstr,0);
text_offset++;
// print radius
Q_snprintf(tempstr,sizeof(tempstr)," radius: %f", m_Radius);
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
static ConCommand shake("shake", CC_Shake, "Shake the screen.", FCVAR_CHEAT );
// Tilt effect
class CEnvTilt : public CPointEntity
{
private:
float m_Duration;
float m_Radius; // radius of 0 means all players
float m_TiltTime;
float m_stopTime;
DECLARE_DATADESC();
public:
DECLARE_CLASS( CEnvShake, CPointEntity );
virtual void Precache( void );
virtual void Spawn( void );
QAngle TiltAngle( void );
inline float Duration( void ) { return m_Duration; }
float Radius( bool bPlayers = true );
inline float TiltTime( void ) { return m_TiltTime; }
inline void SetDuration( float duration ) { m_Duration = duration; }
inline void SetRadius( float radius ) { m_Radius = radius; }
int DrawDebugTextOverlays(void);
// Input handlers
void InputStartTilt( inputdata_t &inputdata );
void InputStopTilt( inputdata_t &inputdata );
// Causes the camera/physics shakes to happen:
void ApplyTilt( ShakeCommand_t command );
};
LINK_ENTITY_TO_CLASS( env_tilt, CEnvTilt );
BEGIN_DATADESC( CEnvTilt )
DEFINE_KEYFIELD( m_Duration, FIELD_FLOAT, "duration" ),
DEFINE_KEYFIELD( m_Radius, FIELD_FLOAT, "radius" ),
DEFINE_KEYFIELD( m_TiltTime, FIELD_TIME, "tilttime" ),
DEFINE_FIELD( m_stopTime, FIELD_TIME ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartTilt", InputStartTilt ),
DEFINE_INPUTFUNC( FIELD_VOID, "StopTilt", InputStopTilt ),
END_DATADESC()
QAngle CEnvTilt::TiltAngle( void )
{
QAngle qTiltAngles = GetAbsAngles();
qTiltAngles.y = 0.0f; // Tilting around the up axis makes no sense
return qTiltAngles;
}
float CEnvTilt::Radius(bool bPlayers)
{
// The radius for players is zero if SF_SHAKE_EVERYONE is set
if ( bPlayers && HasSpawnFlags(SF_SHAKE_EVERYONE))
return 0;
return m_Radius;
}
//-----------------------------------------------------------------------------
// Purpose: Sets default member values when spawning.
//-----------------------------------------------------------------------------
void CEnvTilt::Precache()
{
BaseClass::Precache();
CRopeKeyframe::PrecacheShakeRopes();
}
void CEnvTilt::Spawn( void )
{
Precache();
SetSolid( SOLID_NONE );
SetMoveType( MOVETYPE_NONE );
if ( GetSpawnFlags() & SF_SHAKE_EVERYONE )
{
m_Radius = 0;
}
if ( HasSpawnFlags( SF_SHAKE_NO_VIEW ) && !HasSpawnFlags( SF_SHAKE_PHYSICS ) && !HasSpawnFlags( SF_SHAKE_ROPES ) )
{
DevWarning( "env_shake %s with \"Don't shake view\" spawnflag set without \"Shake physics\" or \"Shake ropes\" spawnflags set.", GetDebugName() );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvTilt::ApplyTilt( ShakeCommand_t command )
{
if ( !HasSpawnFlags( SF_SHAKE_NO_VIEW ) || !HasSpawnFlags( SF_SHAKE_NO_RUMBLE ) )
{
UTIL_ScreenTilt( GetAbsOrigin(), TiltAngle(), Duration(), Radius(), TiltTime(), command, (GetSpawnFlags() & SF_TILT_EASE_INOUT) != 0 );
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that starts the screen shake.
//-----------------------------------------------------------------------------
void CEnvTilt::InputStartTilt( inputdata_t &inputdata )
{
if ( HasSpawnFlags( SF_SHAKE_NO_RUMBLE ) )
{
ApplyTilt( SHAKE_START_NORUMBLE );
}
else if ( HasSpawnFlags( SF_SHAKE_NO_VIEW ) )
{
ApplyTilt( SHAKE_START_RUMBLEONLY );
}
else
{
ApplyTilt( SHAKE_START );
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that stops the screen shake.
//-----------------------------------------------------------------------------
void CEnvTilt::InputStopTilt( inputdata_t &inputdata )
{
ApplyTilt( SHAKE_STOP );
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Returns current text offset from the top
//-----------------------------------------------------------------------------
int CEnvTilt::DrawDebugTextOverlays( void )
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
char tempstr[512];
// print duration
Q_snprintf(tempstr,sizeof(tempstr)," duration: %f", m_Duration);
EntityText(text_offset,tempstr,0);
text_offset++;
// print radius
Q_snprintf(tempstr,sizeof(tempstr)," radius: %f", m_Radius);
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
+223
View File
@@ -0,0 +1,223 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: A point entity that periodically emits sparks and "bzzt" sounds.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "IEffects.h"
#include "engine/IEngineSound.h"
#include "particle_parse.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar fx_new_sparks ( "fx_new_sparks", "1", FCVAR_CHEAT, "Use new style sparks.\n" );
//-----------------------------------------------------------------------------
// Purpose: Emits sparks from the given location and plays a random spark sound.
// Input : pev -
// location -
//-----------------------------------------------------------------------------
void DoSpark( CBaseEntity *ent, const Vector &location, int nMagnitude, int nTrailLength, bool bPlaySound, const Vector &vecDir )
{
g_pEffects->Sparks( location, nMagnitude, nTrailLength, &vecDir );
}
const int SF_SPARK_START_ON = 64;
const int SF_SPARK_GLOW = 128;
const int SF_SPARK_SILENT = 256;
const int SF_SPARK_DIRECTIONAL = 512;
class CEnvSpark : public CPointEntity
{
DECLARE_CLASS( CEnvSpark, CPointEntity );
public:
CEnvSpark( void );
void Spawn(void);
void Precache(void);
void SparkThink(void);
// Input handlers
void InputStartSpark( inputdata_t &inputdata );
void InputStopSpark( inputdata_t &inputdata );
void InputToggleSpark( inputdata_t &inputdata );
void InputSparkOnce( inputdata_t &inputdata );
DECLARE_DATADESC();
float m_flDelay;
int m_nGlowSpriteIndex;
int m_nMagnitude;
int m_nTrailLength;
COutputEvent m_OnSpark;
};
BEGIN_DATADESC( CEnvSpark )
DEFINE_KEYFIELD( m_flDelay, FIELD_FLOAT, "MaxDelay" ),
DEFINE_FIELD( m_nGlowSpriteIndex, FIELD_INTEGER ),
DEFINE_KEYFIELD( m_nMagnitude, FIELD_INTEGER, "Magnitude" ),
DEFINE_KEYFIELD( m_nTrailLength, FIELD_INTEGER, "TrailLength" ),
// Function Pointers
DEFINE_FUNCTION( SparkThink ),
DEFINE_INPUTFUNC( FIELD_VOID, "StartSpark", InputStartSpark ),
DEFINE_INPUTFUNC( FIELD_VOID, "StopSpark", InputStopSpark ),
DEFINE_INPUTFUNC( FIELD_VOID, "ToggleSpark", InputToggleSpark ),
DEFINE_INPUTFUNC( FIELD_VOID, "SparkOnce", InputSparkOnce ),
DEFINE_OUTPUT( m_OnSpark, "OnSpark" ),
END_DATADESC()
LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark);
//-----------------------------------------------------------------------------
// Purpose: Constructor! Exciting, isn't it?
//-----------------------------------------------------------------------------
CEnvSpark::CEnvSpark( void )
{
m_nMagnitude = 1;
m_nTrailLength = 1;
}
//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CEnvSpark::Spawn(void)
{
SetThink( NULL );
SetUse( NULL );
if (FBitSet(m_spawnflags, SF_SPARK_START_ON))
{
SetThink(&CEnvSpark::SparkThink); // start sparking
}
SetNextThink( gpGlobals->curtime + 0.1 + random->RandomFloat( 0, 1.5 ) );
// Negative delays are not allowed
if (m_flDelay < 0)
{
m_flDelay = 0;
}
Precache( );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvSpark::Precache(void)
{
// Unlock string tables to avoid an engine warning
bool oldLock = engine->LockNetworkStringTables( false );
m_nGlowSpriteIndex = PrecacheModel("sprites/glow01.vmt");
engine->LockNetworkStringTables( oldLock );
if ( IsPrecacheAllowed() )
{
PrecacheScriptSound( "DoSpark" );
PrecacheParticleSystem( "env_sparks_omni" );
PrecacheParticleSystem( "env_sparks_directional" );
}
}
extern ConVar phys_pushscale;
//-----------------------------------------------------------------------------
// Purpose: Emits sparks at random intervals.
//-----------------------------------------------------------------------------
void CEnvSpark::SparkThink(void)
{
SetNextThink( gpGlobals->curtime + 0.1 + random->RandomFloat(0, m_flDelay) );
m_OnSpark.FireOutput( this, this );
if ( !( m_spawnflags & SF_SPARK_SILENT ) )
{
EmitSound( "DoSpark" );
}
if ( fx_new_sparks.GetBool() )
{
if ( FBitSet( m_spawnflags, SF_SPARK_DIRECTIONAL ) )
DispatchParticleEffect( "env_sparks_directional", WorldSpaceCenter(), Vector ( m_nMagnitude, m_nTrailLength, FBitSet(m_spawnflags, SF_SPARK_GLOW) ), GetAbsAngles() );
else
DispatchParticleEffect( "env_sparks_omni", WorldSpaceCenter(), Vector ( m_nMagnitude, m_nTrailLength, FBitSet(m_spawnflags, SF_SPARK_GLOW) ), GetAbsAngles() );
}
else
{
Vector vecDir = vec3_origin;
if ( FBitSet( m_spawnflags, SF_SPARK_DIRECTIONAL ) )
{
AngleVectors( GetAbsAngles(), &vecDir );
}
DoSpark( this, WorldSpaceCenter(), m_nMagnitude, m_nTrailLength, !( m_spawnflags & SF_SPARK_SILENT ), vecDir );
if (FBitSet(m_spawnflags, SF_SPARK_GLOW))
{
CPVSFilter filter( GetAbsOrigin() );
te->GlowSprite( filter, 0.0, &GetAbsOrigin(), m_nGlowSpriteIndex, 0.2, 1.5, 25 );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for starting the sparks.
//-----------------------------------------------------------------------------
void CEnvSpark::InputStartSpark( inputdata_t &inputdata )
{
SetThink(&CEnvSpark::SparkThink);
SetNextThink( gpGlobals->curtime );
}
//-----------------------------------------------------------------------------
// Purpose: Shoot one spark.
//-----------------------------------------------------------------------------
void CEnvSpark::InputSparkOnce( inputdata_t &inputdata )
{
SparkThink();
SetNextThink( TICK_NEVER_THINK );
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for starting the sparks.
//-----------------------------------------------------------------------------
void CEnvSpark::InputStopSpark( inputdata_t &inputdata )
{
SetThink(NULL);
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for toggling the on/off state of the sparks.
//-----------------------------------------------------------------------------
void CEnvSpark::InputToggleSpark( inputdata_t &inputdata )
{
if (GetNextThink() == TICK_NEVER_THINK)
{
InputStartSpark( inputdata );
}
else
{
InputStopSpark( inputdata );
}
}
+293
View File
@@ -0,0 +1,293 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "EventLog.h"
#include "team.h"
#include "keyvalues.h"
#include "nav.h"
#include "nav_area.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
CEventLog::CEventLog()
{
}
CEventLog::~CEventLog()
{
}
void CEventLog::FormatPlayer( CBaseEntity *ent, char *str, int len ) const
{
if ( !str || len <= 0 )
{
return;
}
CBasePlayer *player = ToBasePlayer( ent );
const char *playerName = "Unknown";
int userID = 0;
const char *networkIDString = "";
const char *teamName = "";
int areaID = 0;
if ( player )
{
playerName = player->GetPlayerName();
userID = player->GetUserID();
networkIDString = player->GetNetworkIDString();
CTeam *team = player->GetTeam();
if ( team )
{
teamName = team->GetName();
}
}
if ( ent && ent->MyCombatCharacterPointer() )
{
CNavArea *area = ent->MyCombatCharacterPointer()->GetLastKnownArea();
if ( area )
{
areaID = area->GetID();
}
}
V_snprintf( str, len, "\"%s<%i><%s><%s><Area %d>\"", playerName, userID, networkIDString, teamName, areaID );
}
const char *CEventLog::FormatPlayer( CBaseEntity *ent ) const
{
const int MaxEntries = 4;
const int BufferLength = PLAYER_LOGINFO_SIZE;
static char s_buffer[ MaxEntries ][ BufferLength ];
static int s_index = 0;
char *ret = s_buffer[ s_index++ ];
if ( s_index >= MaxEntries )
{
s_index = 0;
}
FormatPlayer( ent, ret, BufferLength );
return ret;
}
void CEventLog::FireGameEvent( IGameEvent *event )
{
if ( !g_bIsLogging )
return;
PrintEvent ( event );
}
bool CEventLog::PrintEvent( IGameEvent *event )
{
const char * name = event->GetName();
if ( StringHasPrefixCaseSensitive( name, "server_" ) )
{
return true; // we don't care about server events (engine does)
}
else if ( StringHasPrefixCaseSensitive( name, "player_" ) )
{
return PrintPlayerEvent( event );
}
else if ( StringHasPrefixCaseSensitive( name, "team_" ) )
{
return PrintTeamEvent( event );
}
else if ( StringHasPrefixCaseSensitive( name, "game_" ) )
{
return PrintGameEvent( event );
}
else
{
return PrintOtherEvent( event ); // bomb_, round_, et al
}
}
bool CEventLog::PrintGameEvent( IGameEvent *event )
{
// const char * name = event->GetName() + Q_strlen("game_"); // remove prefix
return false;
}
bool CEventLog::PrintPlayerEvent( IGameEvent *event )
{
const char * eventName = event->GetName();
const int userid = event->GetInt( "userid" );
if ( StringHasPrefixCaseSensitive( eventName, "player_connect" ) ) // player connect is before the CBasePlayer pointer is setup
{
const char *name = event->GetString( "name" );
const char *address = event->GetString( "address" );
const char *networkid = event->GetString("networkid" );
UTIL_LogPrintf( "\"%s<%i><%s><>\" connected, address \"%s\"\n", name, userid, networkid, address);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "player_disconnect" ) )
{
const char *reason = event->GetString("reason" );
const char *name = event->GetString("name" );
const char *networkid = event->GetString("networkid" );
CTeam *team = NULL;
CBasePlayer *pPlayer = UTIL_PlayerByUserId( userid );
if ( pPlayer )
{
team = pPlayer->GetTeam();
}
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" disconnected (reason \"%s\")\n", name, userid, networkid, team ? team->GetName() : "", reason );
return true;
}
CBasePlayer *pPlayer = UTIL_PlayerByUserId( userid );
if ( !pPlayer)
{
DevMsg( "CEventLog::PrintPlayerEvent: Failed to find player (userid: %i, event: %s)\n", userid, eventName );
return false;
}
if ( StringHasPrefixCaseSensitive( eventName, "player_team" ) )
{
const bool bDisconnecting = event->GetBool( "disconnect" );
if ( !bDisconnecting )
{
const int newTeam = event->GetInt( "team" );
const int oldTeam = event->GetInt( "oldteam" );
CTeam *team = GetGlobalTeam( newTeam );
CTeam *oldteam = GetGlobalTeam( oldTeam );
const char *playerName = pPlayer->GetPlayerName();
int userID = pPlayer->GetUserID();
if ( userID > 0 )
{
const char *networkID = pPlayer->GetNetworkIDString();
const char *oldTeamName = (oldteam) ? oldteam->GetName() : "Unassigned";
const char *teamName = (team) ? team->GetName() : "Unassigned";
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" joined team \"%s\"\n",
playerName,
userID,
networkID,
oldTeamName,
teamName );
}
}
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "player_death" ) )
{
const int attackerid = event->GetInt("attacker" );
CBasePlayer *pAttacker = UTIL_PlayerByUserId( attackerid );
CTeam *team = pPlayer->GetTeam();
CTeam *attackerTeam = NULL;
if ( pAttacker )
{
attackerTeam = pAttacker->GetTeam();
}
if ( pPlayer == pAttacker && pPlayer )
{
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
team ? team->GetName() : "",
pAttacker->GetClassname()
);
}
else if ( pAttacker )
{
CTeam *attackerTeam = pAttacker->GetTeam();
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\"\n",
pAttacker->GetPlayerName(),
attackerid,
pAttacker->GetNetworkIDString(),
attackerTeam ? attackerTeam->GetName() : "",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
team ? team->GetName() : ""
);
}
else
{
// killed by the world
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"world\"\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString(),
team ? team->GetName() : ""
);
}
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "player_activate" ) )
{
UTIL_LogPrintf( "\"%s<%i><%s><>\" entered the game\n",
pPlayer->GetPlayerName(),
userid,
pPlayer->GetNetworkIDString()
);
return true;
}
else if ( StringHasPrefixCaseSensitive( eventName, "player_changename" ) )
{
const char *newName = event->GetString( "newname" );
const char *oldName = event->GetString( "oldname" );
CTeam *team = pPlayer->GetTeam();
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" changed name to \"%s\"\n",
oldName,
userid,
pPlayer->GetNetworkIDString(),
team ? team->GetName() : "",
newName
);
return true;
}
// ignored events
//player_hurt
return false;
}
bool CEventLog::PrintTeamEvent( IGameEvent *event )
{
// const char * name = event->GetName() + Q_strlen("team_"); // remove prefix
return false;
}
bool CEventLog::PrintOtherEvent( IGameEvent *event )
{
return false;
}
bool CEventLog::Init()
{
ListenForGameEvent( "player_changename" );
ListenForGameEvent( "player_activate" );
ListenForGameEvent( "player_death" );
ListenForGameEvent( "player_team" );
ListenForGameEvent( "player_disconnect" );
ListenForGameEvent( "player_connect" );
return true;
}
+54
View File
@@ -0,0 +1,54 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#if !defined EVENTLOG_H
#define EVENTLOG_H
#ifdef _WIN32
#pragma once
#endif
#include "GameEventListener.h"
#include <igamesystem.h>
class CEventLog : public CGameEventListener, public CBaseGameSystem
{
public:
CEventLog();
virtual ~CEventLog();
public: // IGameEventListener Interface
virtual void FireGameEvent( IGameEvent * event );
public: // CBaseGameSystem overrides
virtual bool Init();
//virtual void Shutdown() {}
virtual void FormatPlayer( CBaseEntity *ent, char *str, int len ) const;
const char *FormatPlayer( CBaseEntity *ent ) const;
enum
{
PLAYER_LOGINFO_SIZE = 256,
};
protected:
virtual bool PrintEvent( IGameEvent * event );
virtual bool PrintGameEvent( IGameEvent * event );
virtual bool PrintPlayerEvent( IGameEvent * event );
virtual bool PrintTeamEvent( IGameEvent * event );
virtual bool PrintOtherEvent( IGameEvent * event );
};
extern CEventLog *GameLogSystem();
#endif // EVENTLOG_H
@@ -0,0 +1,187 @@
//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "GameStats.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
void BasicGameStatsRecord_t::Clear()
{
m_nCount = 0;
m_nSeconds = 0;
m_nCommentary = 0;
m_nHDR = 0;
m_nCaptions = 0;
m_bSteam = true;
m_bCyberCafe = false;
Q_memset( m_nSkill, 0, sizeof( m_nSkill ) );
m_nDeaths = 0;
}
void BasicGameStatsRecord_t::SaveToBuffer( CUtlBuffer &buf )
{
buf.PutInt( m_nCount );
buf.PutInt( m_nSeconds );
buf.PutInt( m_nCommentary );
buf.PutInt( m_nHDR );
buf.PutInt( m_nCaptions );
for ( int i = 0; i < 3; ++i )
{
buf.PutInt( m_nSkill[ i ] );
}
buf.PutChar( m_bSteam ? 1 : 0 );
buf.PutChar( m_bCyberCafe ? 1 : 0 );
buf.PutInt( m_nDeaths );
}
bool BasicGameStatsRecord_t::ParseFromBuffer( CUtlBuffer &buf, int iBufferStatsVersion )
{
bool bret = true;
m_nCount = buf.GetInt();
if ( m_nCount > 100000 || m_nCount < 0 )
{
bret = false;
}
m_nSeconds = buf.GetInt();
// Note, don't put the buf.GetInt() in the macro since it'll get evaluated twice!!!
m_nSeconds = MAX( m_nSeconds, 0 );
m_nCommentary = buf.GetInt();
if ( m_nCommentary < 0 || m_nCommentary > 100000 )
{
bret = false;
}
m_nHDR = buf.GetInt();
if ( m_nHDR < 0 || m_nHDR > 100000 )
{
bret = false;
}
m_nCaptions = buf.GetInt();
if ( m_nCaptions < 0 || m_nCaptions > 100000 )
{
bret = false;
}
for ( int i = 0; i < 3; ++i )
{
m_nSkill[ i ] = buf.GetInt();
if ( m_nSkill[ i ] < 0 || m_nSkill[ i ] > 100000 )
{
bret = false;
}
}
if ( iBufferStatsVersion > GAMESTATS_FILE_VERSION_OLD )
{
m_bSteam = buf.GetChar() ? true : false;
}
if ( iBufferStatsVersion > GAMESTATS_FILE_VERSION_OLD2 )
{
m_bCyberCafe = buf.GetChar() ? true : false;
}
if ( iBufferStatsVersion > GAMESTATS_FILE_VERSION_OLD5 )
{
m_nDeaths = buf.GetInt();
}
return bret;
}
void BasicGameStats_t::Clear()
{
m_nSecondsToCompleteGame = 0;
m_Summary.Clear();
m_MapTotals.Purge();
}
void BasicGameStats_t::SaveToBuffer( CUtlBuffer& buf )
{
buf.PutInt( m_nSecondsToCompleteGame );
m_Summary.SaveToBuffer( buf );
int c = m_MapTotals.Count();
buf.PutInt( c );
for ( int i = m_MapTotals.First(); i != m_MapTotals.InvalidIndex(); i = m_MapTotals.Next( i ) )
{
char const *name = m_MapTotals.GetElementName( i );
BasicGameStatsRecord_t &rec = m_MapTotals[ i ];
buf.PutString( name );
rec.SaveToBuffer( buf );
}
buf.PutChar( 0 );
buf.PutChar( m_bSteam ? 1 : 0 );
buf.PutChar( m_bCyberCafe ? 1 : 0 );
buf.PutShort( (short)m_nDXLevel );
}
BasicGameStatsRecord_t *BasicGameStats_t::FindOrAddRecordForMap( char const *mapname )
{
int idx = m_MapTotals.Find( mapname );
if ( idx == m_MapTotals.InvalidIndex() )
{
idx = m_MapTotals.Insert( mapname );
}
return &m_MapTotals[ idx ];
}
bool BasicGameStats_t::ParseFromBuffer( CUtlBuffer& buf, int iBufferStatsVersion )
{
bool bret = true;
m_nSecondsToCompleteGame = buf.GetInt();
if ( m_nSecondsToCompleteGame < 0 || m_nSecondsToCompleteGame > 10000000 )
{
bret = false;
}
m_Summary.ParseFromBuffer( buf, iBufferStatsVersion );
int c = buf.GetInt();
if ( c > 1024 || c < 0 )
{
bret = false;
}
for ( int i = 0; i < c; ++i )
{
char mapname[ 256 ];
buf.GetString( mapname, sizeof( mapname ) );
BasicGameStatsRecord_t *rec = FindOrAddRecordForMap( mapname );
bool valid= rec->ParseFromBuffer( buf, iBufferStatsVersion );
if ( !valid )
{
bret = false;
}
}
if ( iBufferStatsVersion >= GAMESTATS_FILE_VERSION_OLD2 )
{
buf.GetChar(); // Gets the obsolete hl2 unlocked chapter field
m_bSteam = buf.GetChar() ? true : false;
}
if ( iBufferStatsVersion > GAMESTATS_FILE_VERSION_OLD2 )
{
m_bCyberCafe = buf.GetChar() ? true : false;
}
if ( iBufferStatsVersion > GAMESTATS_FILE_VERSION_OLD3 )
{
m_nDXLevel = (int)buf.GetShort();
}
return bret;
}
+289
View File
@@ -0,0 +1,289 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Material modify control entity.
//
//=============================================================================//
#include "cbase.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//------------------------------------------------------------------------------
// FIXME: This really should inherit from something more lightweight.
//------------------------------------------------------------------------------
#define MATERIAL_MODIFY_STRING_SIZE 255
#define MATERIAL_MODIFY_ANIMATION_UNSET -1
// Must match C_MaterialModifyControl.cpp
enum MaterialModifyMode_t
{
MATERIAL_MODIFY_MODE_NONE = 0,
MATERIAL_MODIFY_MODE_SETVAR = 1,
MATERIAL_MODIFY_MODE_ANIM_SEQUENCE = 2,
MATERIAL_MODIFY_MODE_FLOAT_LERP = 3,
};
ConVar debug_materialmodifycontrol( "debug_materialmodifycontrol", "0" );
class CMaterialModifyControl : public CBaseEntity
{
public:
DECLARE_CLASS( CMaterialModifyControl, CBaseEntity );
CMaterialModifyControl();
void Spawn( void );
bool KeyValue( const char *szKeyName, const char *szValue );
int UpdateTransmitState();
int ShouldTransmit( const CCheckTransmitInfo *pInfo );
void SetMaterialVar( inputdata_t &inputdata );
void SetMaterialVarToCurrentTime( inputdata_t &inputdata );
void InputStartAnimSequence( inputdata_t &inputdata );
void InputStartFloatLerp( inputdata_t &inputdata );
virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
private:
CNetworkString( m_szMaterialName, MATERIAL_MODIFY_STRING_SIZE );
CNetworkString( m_szMaterialVar, MATERIAL_MODIFY_STRING_SIZE );
CNetworkString( m_szMaterialVarValue, MATERIAL_MODIFY_STRING_SIZE );
CNetworkVar( int, m_iFrameStart );
CNetworkVar( int, m_iFrameEnd );
CNetworkVar( bool, m_bWrap );
CNetworkVar( float, m_flFramerate );
CNetworkVar( bool, m_bNewAnimCommandsSemaphore );
CNetworkVar( float, m_flFloatLerpStartValue );
CNetworkVar( float, m_flFloatLerpEndValue );
CNetworkVar( float, m_flFloatLerpTransitionTime );
CNetworkVar( int, m_nModifyMode );
};
LINK_ENTITY_TO_CLASS(material_modify_control, CMaterialModifyControl);
BEGIN_DATADESC( CMaterialModifyControl )
// Variables.
DEFINE_AUTO_ARRAY( m_szMaterialName, FIELD_CHARACTER ),
DEFINE_AUTO_ARRAY( m_szMaterialVar, FIELD_CHARACTER ),
DEFINE_AUTO_ARRAY( m_szMaterialVarValue, FIELD_CHARACTER ),
DEFINE_FIELD( m_iFrameStart, FIELD_INTEGER ),
DEFINE_FIELD( m_iFrameEnd, FIELD_INTEGER ),
DEFINE_FIELD( m_bWrap, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flFramerate, FIELD_FLOAT ),
DEFINE_FIELD( m_bNewAnimCommandsSemaphore, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flFloatLerpStartValue, FIELD_FLOAT ),
DEFINE_FIELD( m_flFloatLerpEndValue, FIELD_FLOAT ),
DEFINE_FIELD( m_flFloatLerpTransitionTime, FIELD_FLOAT ),
DEFINE_FIELD( m_nModifyMode, FIELD_INTEGER ),
// Inputs.
DEFINE_INPUTFUNC( FIELD_STRING, "SetMaterialVar", SetMaterialVar ),
DEFINE_INPUTFUNC( FIELD_VOID, "SetMaterialVarToCurrentTime", SetMaterialVarToCurrentTime ),
DEFINE_INPUTFUNC( FIELD_STRING, "StartAnimSequence", InputStartAnimSequence ),
DEFINE_INPUTFUNC( FIELD_STRING, "StartFloatLerp", InputStartFloatLerp ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST(CMaterialModifyControl, DT_MaterialModifyControl)
SendPropString( SENDINFO( m_szMaterialName ) ),
SendPropString( SENDINFO( m_szMaterialVar ) ),
SendPropString( SENDINFO( m_szMaterialVarValue ) ),
SendPropInt( SENDINFO(m_iFrameStart), 8 ),
SendPropInt( SENDINFO(m_iFrameEnd), 8 ),
SendPropInt( SENDINFO(m_bWrap), 1, SPROP_UNSIGNED ),
SendPropFloat( SENDINFO(m_flFramerate), 0, SPROP_NOSCALE ),
SendPropInt( SENDINFO(m_bNewAnimCommandsSemaphore), 1, SPROP_UNSIGNED ),
SendPropFloat( SENDINFO(m_flFloatLerpStartValue), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO(m_flFloatLerpEndValue), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO(m_flFloatLerpTransitionTime), 0, SPROP_NOSCALE ),
SendPropInt( SENDINFO(m_nModifyMode), 2, SPROP_UNSIGNED ),
END_SEND_TABLE()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CMaterialModifyControl::CMaterialModifyControl()
{
m_iFrameStart = MATERIAL_MODIFY_ANIMATION_UNSET;
m_iFrameEnd = MATERIAL_MODIFY_ANIMATION_UNSET;
m_nModifyMode = MATERIAL_MODIFY_MODE_NONE;
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CMaterialModifyControl::Spawn( void )
{
Precache();
SetSolid( SOLID_NONE );
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
bool CMaterialModifyControl::KeyValue( const char *szKeyName, const char *szValue )
{
if ( FStrEq( szKeyName, "materialName" ) )
{
Q_strncpy( m_szMaterialName.GetForModify(), szValue, MATERIAL_MODIFY_STRING_SIZE );
return true;
}
if ( FStrEq( szKeyName, "materialVar" ) )
{
Q_strncpy( m_szMaterialVar.GetForModify(), szValue, MATERIAL_MODIFY_STRING_SIZE );
return true;
}
return BaseClass::KeyValue( szKeyName, szValue );
}
//------------------------------------------------------------------------------
// Purpose : Send even though we don't have a model.
//------------------------------------------------------------------------------
int CMaterialModifyControl::UpdateTransmitState()
{
// ALWAYS transmit to all clients.
return SetTransmitState( FL_EDICT_FULLCHECK );
}
//-----------------------------------------------------------------------------
// Send if the parent is being sent:
//-----------------------------------------------------------------------------
int CMaterialModifyControl::ShouldTransmit( const CCheckTransmitInfo *pInfo )
{
CBaseEntity *pEnt = GetMoveParent();
if ( pEnt )
{
return pEnt->ShouldTransmit( pInfo );
}
return FL_EDICT_DONTSEND;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMaterialModifyControl::SetMaterialVar( inputdata_t &inputdata )
{
//if( debug_materialmodifycontrol.GetBool() && Q_stristr( GetDebugName(), "alyx" ) )
//{
//DevMsg( 1, "CMaterialModifyControl::SetMaterialVar %s %s %s=\"%s\"\n",
//GetDebugName(), m_szMaterialName.Get(), m_szMaterialVar.Get(), inputdata.value.String() );
//}
Q_strncpy( m_szMaterialVarValue.GetForModify(), inputdata.value.String(), MATERIAL_MODIFY_STRING_SIZE );
m_nModifyMode = MATERIAL_MODIFY_MODE_SETVAR;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMaterialModifyControl::SetMaterialVarToCurrentTime( inputdata_t &inputdata )
{
char temp[32];
Q_snprintf( temp, 32, "%f", gpGlobals->curtime );
Q_strncpy( m_szMaterialVarValue.GetForModify(), temp, MATERIAL_MODIFY_STRING_SIZE );
m_nModifyMode = MATERIAL_MODIFY_MODE_SETVAR;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMaterialModifyControl::InputStartAnimSequence( inputdata_t &inputdata )
{
char parseString[255];
Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString));
// Get the start & end frames
char *pszParam = strtok(parseString," ");
if ( pszParam && pszParam[0] )
{
int iFrameStart = atoi(pszParam);
pszParam = strtok(NULL," ");
if ( pszParam && pszParam[0] )
{
int iFrameEnd = atoi(pszParam);
pszParam = strtok(NULL," ");
if ( pszParam && pszParam[0] )
{
float flFramerate = atof(pszParam);
pszParam = strtok(NULL," ");
if ( pszParam && pszParam[0] )
{
bool bWrap = atoi(pszParam) != 0;
// Got all the parameters. Save 'em and return;
m_iFrameStart = iFrameStart;
m_iFrameEnd = iFrameEnd;
m_flFramerate = flFramerate;
m_bWrap = bWrap;
m_nModifyMode = MATERIAL_MODIFY_MODE_ANIM_SEQUENCE;
m_bNewAnimCommandsSemaphore = !m_bNewAnimCommandsSemaphore;
return;
}
}
}
}
Warning("%s (%s) received StartAnimSequence input without correct parameters. Syntax: <Frame Start> <Frame End> <Frame Rate> <Loop>\nSetting <Frame End> to -1 uses the last frame of the texture. <Loop> should be 1 or 0.\n", GetClassname(), GetDebugName() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMaterialModifyControl::InputStartFloatLerp( inputdata_t &inputdata )
{
char parseString[255];
Q_strncpy(parseString, inputdata.value.String(), sizeof(parseString));
// if( debug_materialmodifycontrol.GetBool() )//&& Q_stristr( GetDebugName(), "alyx" ) )
// {
// DevMsg( 1, "CMaterialModifyControl::InputStartFloatLerp %s %s %s \"%s\"\n",
// GetDebugName(), m_szMaterialName.Get(), m_szMaterialVar.Get(), inputdata.value.String() );
// }
// Get the start & end values
char *pszParam = strtok(parseString," ");
if ( pszParam && pszParam[0] )
{
float flStartValue = atof(pszParam);
pszParam = strtok(NULL," ");
if ( pszParam && pszParam[0] )
{
float flEndValue = atof(pszParam);
pszParam = strtok(NULL," ");
if ( pszParam && pszParam[0] )
{
float flTransitionTime = atof(pszParam);
pszParam = strtok(NULL," ");
if ( pszParam && pszParam[0] )
{
bool bWrap = atoi(pszParam) != 0;
// We don't implement wrap currently.
bWrap = bWrap;
// Got all the parameters. Save 'em and return;
m_flFloatLerpStartValue = flStartValue;
m_flFloatLerpEndValue = flEndValue;
m_flFloatLerpTransitionTime = flTransitionTime;
m_nModifyMode = MATERIAL_MODIFY_MODE_FLOAT_LERP;
m_bNewAnimCommandsSemaphore = !m_bNewAnimCommandsSemaphore;
return;
}
}
}
}
Warning("%s (%s) received StartFloatLerp input without correct parameters. Syntax: <Start Value> <End Value> <Transition Time> <Loop>\n<Loop> should be 1 or 0.\n", GetClassname(), GetDebugName() );
}
@@ -0,0 +1,118 @@
// BehaviorBackUp.h
// Back up for a short duration
// Author: Michael Booth, March 2007
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _BEHAVIOR_BACK_UP_H_
#define _BEHAVIOR_BACK_UP_H_
//----------------------------------------------------------------------------------------------
/**
* Move backwards for a short duration away from a given position.
* Useful to dislodge ourselves if we get stuck while following our path.
*/
template < typename Actor >
class BehaviorBackUp : public Action< Actor >
{
public:
BehaviorBackUp( const Vector &avoidPos );
virtual ActionResult< Actor > OnStart( Actor *me, Action< Actor > *priorAction );
virtual ActionResult< Actor > Update( Actor *me, float interval );
virtual EventDesiredResult< Actor > OnStuck( Actor *me );
virtual const char *GetName( void ) const { return "BehaviorBackUp"; }
private:
CountdownTimer m_giveUpTimer;
CountdownTimer m_backupTimer;
CountdownTimer m_jumpTimer;
Vector m_way;
Vector m_avoidPos;
};
//----------------------------------------------------------------------------------------------
template < typename Actor >
inline BehaviorBackUp< Actor >::BehaviorBackUp( const Vector &avoidPos )
{
m_avoidPos = avoidPos;
}
//----------------------------------------------------------------------------------------------
template < typename Actor >
inline ActionResult< Actor > BehaviorBackUp< Actor >::OnStart( Actor *me, Action< Actor > *priorAction )
{
ILocomotion *mover = me->GetLocomotionInterface();
// don't back off if we're on a ladder
if ( mover && mover->IsUsingLadder() )
{
return Done();
}
float backupTime = RandomFloat( 0.3f, 0.5f );
m_backupTimer.Start( backupTime );
m_jumpTimer.Start( 1.5f * backupTime );
m_giveUpTimer.Start( 2.5f * backupTime );
m_way = me->GetPosition() - m_avoidPos;
m_way.NormalizeInPlace();
return Continue();
}
//----------------------------------------------------------------------------------------------
template < typename Actor >
inline ActionResult< Actor > BehaviorBackUp< Actor >::Update( Actor *me, float interval )
{
if ( m_giveUpTimer.IsElapsed() )
{
return Done();
}
// if ( m_jumpTimer.HasStarted() && m_jumpTimer.IsElapsed() )
// {
// me->GetLocomotionInterface()->Jump();
// m_jumpTimer.Invalidate();
// }
ILocomotion *mover = me->GetLocomotionInterface();
if ( mover )
{
Vector goal;
if ( m_backupTimer.IsElapsed() )
{
// move towards bad spot
goal = m_avoidPos; // me->GetPosition() - 100.0f * m_way;
}
else
{
// move away from bad spot
goal = me->GetPosition() + 100.0f * m_way;
}
mover->Approach( goal );
}
return Continue();
}
//----------------------------------------------------------------------------------------------
template < typename Actor >
inline EventDesiredResult< Actor > BehaviorBackUp< Actor >::OnStuck( Actor *me )
{
return TryToSustain( RESULT_IMPORTANT, "Stuck while trying to back up" );
}
#endif // _BEHAVIOR_BACK_UP_H_
@@ -0,0 +1,82 @@
// BehaviorMoveTo.h
// Move to a potentially far away position
// Author: Michael Booth, June 2007
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _BEHAVIOR_MOVE_TO_H_
#define _BEHAVIOR_MOVE_TO_H_
//----------------------------------------------------------------------------------------------
/**
* Move to a potentially far away position, using path planning.
*/
template < typename Actor, typename PathCost >
class BehaviorMoveTo : public Action< Actor >
{
public:
BehaviorMoveTo( const Vector &goal );
virtual ActionResult< Actor > OnStart( Actor *me, Action< Actor > *priorAction );
virtual ActionResult< Actor > Update( Actor *me, float interval );
// derive to supply specific cost functor - default uses simple shortest path
virtual bool ComputePath( Actor *me, const Vector &goal, PathFollower *path );
virtual const char *GetName( void ) const { return "BehaviorMoveTo"; }
private:
Vector m_goal;
PathFollower m_path;
};
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline BehaviorMoveTo< Actor, PathCost >::BehaviorMoveTo( const Vector &goal )
{
m_goal = goal;
m_path.Invalidate();
}
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline bool BehaviorMoveTo< Actor, PathCost >::ComputePath( Actor *me, const Vector &goal, PathFollower *path )
{
PathCost cost( me );
return path->Compute( me, goal, cost );
}
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline ActionResult< Actor > BehaviorMoveTo< Actor, PathCost >::OnStart( Actor *me, Action< Actor > *priorAction )
{
if ( !ComputePath( me, m_goal, &m_path ) )
{
return Done( "No path to goal" );
}
return Continue();
}
//----------------------------------------------------------------------------------------------
template < typename Actor, typename PathCost >
inline ActionResult< Actor > BehaviorMoveTo< Actor, PathCost >::Update( Actor *me, float interval )
{
// move along path
m_path.Update( me );
if ( m_path.IsValid() )
{
return Continue();
}
return Done();
}
#endif // _BEHAVIOR_MOVE_TO_H_
+522
View File
@@ -0,0 +1,522 @@
// NextBotCombatCharacter.cpp
// Next generation bot system
// Author: Michael Booth, April 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "team.h"
#include "CRagdollMagnet.h"
#include "NextBot.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotBodyInterface.h"
#ifdef TERROR
#include "TerrorGamerules.h"
#endif
#include "datacache/imdlcache.h"
#include "EntityFlame.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar NextBotStop( "nb_stop", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Stop all NextBots" );
//--------------------------------------------------------------------------------------------------------
class CSendBotCommand
{
public:
CSendBotCommand( const char *command )
{
m_command = command;
}
bool operator() ( INextBot *bot )
{
bot->OnCommandString( m_command );
return true;
}
const char *m_command;
};
CON_COMMAND_F( nb_command, "Sends a command string to all bots", FCVAR_CHEAT )
{
if ( args.ArgC() <= 1 )
{
Msg( "Missing command string" );
return;
}
CSendBotCommand sendCmd( args.ArgS() );
TheNextBots().ForEachBot( sendCmd );
}
//-----------------------------------------------------------------------------------------------------
BEGIN_DATADESC( NextBotCombatCharacter )
DEFINE_THINKFUNC( DoThink ),
END_DATADESC()
//-----------------------------------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST( NextBotCombatCharacter, DT_NextBot )
END_SEND_TABLE()
//-----------------------------------------------------------------------------------------------------
NextBotDestroyer::NextBotDestroyer( int team )
{
m_team = team;
}
//-----------------------------------------------------------------------------------------------------
bool NextBotDestroyer::operator() ( INextBot *bot )
{
if ( m_team == TEAM_ANY || bot->GetEntity()->GetTeamNumber() == m_team )
{
// players need to be kicked, not deleted
if ( bot->GetEntity()->IsPlayer() )
{
CBasePlayer *player = dynamic_cast< CBasePlayer * >( bot->GetEntity() );
engine->ServerCommand( UTIL_VarArgs( "kick \"%s\"\n", player->GetPlayerName() ) );
}
else
{
UTIL_Remove( bot->GetEntity() );
}
}
return true;
}
//-----------------------------------------------------------------------------------------------------
CON_COMMAND_F( nb_delete_all, "Delete all non-player NextBot entities.", FCVAR_CHEAT )
{
// Listenserver host or rcon access only!
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
CTeam *team = NULL;
if ( args.ArgC() == 2 )
{
const char *teamName = args[1];
for( int i=0; i < g_Teams.Count(); ++i )
{
if ( FStrEq( teamName, g_Teams[i]->GetName() ) )
{
// delete all bots on this team
team = g_Teams[i];
break;
}
}
if ( team == NULL )
{
Msg( "Invalid team '%s'\n", teamName );
return;
}
}
// delete all bots on all teams
NextBotDestroyer destroyer( team ? team->GetTeamNumber() : TEAM_ANY );
TheNextBots().ForEachBot( destroyer );
}
//-----------------------------------------------------------------------------------------------------
class NextBotApproacher
{
public:
NextBotApproacher( void )
{
CBasePlayer *player = UTIL_GetListenServerHost();
if ( player )
{
Vector forward;
player->EyeVectors( &forward );
trace_t result;
unsigned int mask = MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_GRATE | CONTENTS_WINDOW;
UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, mask, player, COLLISION_GROUP_NONE, &result );
if ( result.DidHit() )
{
NDebugOverlay::Cross3D( result.endpos, 5, 0, 255, 0, true, 10.0f );
m_isGoalValid = true;
m_goal = result.endpos;
}
else
{
m_isGoalValid = false;
}
}
}
bool operator() ( INextBot *bot )
{
if ( TheNextBots().IsDebugFilterMatch( bot ) )
{
bot->OnCommandApproach( m_goal );
}
return true;
}
bool m_isGoalValid;
Vector m_goal;
};
CON_COMMAND_F( nb_move_to_cursor, "Tell all NextBots to move to the cursor position", FCVAR_CHEAT )
{
// Listenserver host or rcon access only!
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
NextBotApproacher approach;
TheNextBots().ForEachBot( approach );
}
//----------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------
bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
return ( entity->MyNextBotPointer() == NULL && !entity->IsPlayer() );
}
//----------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------
bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
{
// Honor BlockLOS also to allow seeing through partially-broken doors
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
return ( entity->MyNextBotPointer() == NULL && !entity->IsPlayer() && entity->BlocksLOS() );
}
//----------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------
NextBotCombatCharacter::NextBotCombatCharacter( void )
{
m_lastAttacker = NULL;
m_didModelChange = false;
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Spawn( void )
{
BaseClass::Spawn();
// reset bot components
Reset();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_CUSTOM );
SetCollisionGroup( COLLISION_GROUP_PLAYER );
m_iMaxHealth = m_iHealth;
m_takedamage = DAMAGE_YES;
MDLCACHE_CRITICAL_SECTION();
InitBoneControllers( );
// set up think callback
SetThink( &NextBotCombatCharacter::DoThink );
SetNextThink( gpGlobals->curtime );
m_lastAttacker = NULL;
}
bool NextBotCombatCharacter::IsAreaTraversable( const CNavArea *area ) const
{
if ( !area )
return false;
ILocomotion *mover = GetLocomotionInterface();
if ( mover && !mover->IsAreaTraversable( area ) )
return false;
return BaseClass::IsAreaTraversable( area );
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::DoThink( void )
{
VPROF_BUDGET( "NextBotCombatCharacter::DoThink", "NextBot" );
SetNextThink( gpGlobals->curtime );
if ( BeginUpdate() )
{
// emit model change event
if ( m_didModelChange )
{
m_didModelChange = false;
OnModelChanged();
// propagate model change into NextBot event responders
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnModelChanged();
}
}
UpdateLastKnownArea();
// update bot components
if ( !NextBotStop.GetBool() && (GetFlags() & FL_FROZEN) == 0 )
{
Update();
}
EndUpdate();
}
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Touch( CBaseEntity *other )
{
if ( ShouldTouch( other ) )
{
// propagate touch into NextBot event responders
trace_t result;
result = GetTouchTrace();
// OnContact refers to *physical* contact, not triggers or other non-physical entities
if ( result.DidHit() || other->MyCombatCharacterPointer() != NULL )
{
OnContact( other, &result );
}
}
BaseClass::Touch( other );
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::SetModel( const char *szModelName )
{
// actually change the model
BaseClass::SetModel( szModelName );
// need to do a lazy-check because precache system also invokes this
m_didModelChange = true;
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
{
BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
// propagate event to components
OnIgnite();
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::Ignite( float flFlameLifetime, CBaseEntity *pAttacker )
{
if ( IsOnFire() )
return;
// BaseClass::Ignite stuff, plus SetAttacker on the flame, so our attacker gets credit
CEntityFlame *pFlame = CEntityFlame::Create( this, flFlameLifetime );
if ( pFlame )
{
pFlame->SetAttacker( pAttacker );
AddFlag( FL_ONFIRE );
SetEffectEntity( pFlame );
}
m_OnIgnite.FireOutput( this, this );
// propagate event to components
OnIgnite();
}
//----------------------------------------------------------------------------------------------------------
int NextBotCombatCharacter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
// track our last attacker
if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
{
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
}
// propagate event to components
OnInjured( info );
return CBaseCombatCharacter::OnTakeDamage_Alive( info );
}
//----------------------------------------------------------------------------------------------------------
int NextBotCombatCharacter::OnTakeDamage_Dying( const CTakeDamageInfo &info )
{
// track our last attacker
if ( info.GetAttacker()->MyCombatCharacterPointer() )
{
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
}
// propagate event to components
OnInjured( info );
return CBaseCombatCharacter::OnTakeDamage_Dying( info );
}
//----------------------------------------------------------------------------------------------------------
/**
* Can't use CBaseCombatCharacter's Event_Killed because it will immediately ragdoll us
*/
static int g_DeathStartEvent = 0;
void NextBotCombatCharacter::Event_Killed( const CTakeDamageInfo &info )
{
// track our last attacker
if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
{
m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
}
// propagate event to my components
OnKilled( info );
// Advance life state to dying
m_lifeState = LIFE_DYING;
#ifdef TERROR
/*
* TODO: Make this game-generic
*/
// Create the death event just like players do.
TerrorGameRules()->DeathNoticeForEntity( this, info );
// Infected specific event
TerrorGameRules()->DeathNoticeForInfected( this, info );
#endif
if ( GetOwnerEntity() != NULL )
{
GetOwnerEntity()->DeathNotice( this );
}
// inform the other bots
TheNextBots().OnKilled( this, info );
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity )
{
ILocomotion *mover = GetLocomotionInterface();
if ( mover )
{
// hack to keep ground entity from being NULL'd when Z velocity is positive
SetGroundEntity( mover->GetGround() );
}
}
//----------------------------------------------------------------------------------------------------------
bool NextBotCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector )
{
// See if there's a ragdoll magnet that should influence our force.
Vector adjustedForceVector = forceVector;
CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( this );
if ( magnet )
{
adjustedForceVector += magnet->GetForceVector( this );
}
// clear the deceased's sound channels.(may have been firing or reloading when killed)
EmitSound( "BaseCombatCharacter.StopWeaponSounds" );
return BaseClass::BecomeRagdoll( info, adjustedForceVector );
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::HandleAnimEvent( animevent_t *event )
{
// propagate event to components
OnAnimationEvent( event );
}
//----------------------------------------------------------------------------------------------------------
/**
* Propagate event into NextBot event responders
*/
void NextBotCombatCharacter::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
{
INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
BaseClass::OnNavAreaChanged( enteredArea, leftArea );
}
//----------------------------------------------------------------------------------------------------------
Vector NextBotCombatCharacter::EyePosition( void )
{
if ( GetBodyInterface() )
{
return GetBodyInterface()->GetEyePosition();
}
return BaseClass::EyePosition();
}
//----------------------------------------------------------------------------------------------------------
/**
* Return true if this object can be +used by the bot
*/
bool NextBotCombatCharacter::IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps )
{
if ( entity )
{
int caps = entity->ObjectCaps();
if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) )
{
if ( (caps & requiredCaps) == requiredCaps )
{
return true;
}
}
}
return false;
}
//----------------------------------------------------------------------------------------------------------
void NextBotCombatCharacter::UseEntity( CBaseEntity *entity, USE_TYPE useType )
{
if ( IsUseableEntity( entity ) )
{
variant_t emptyVariant;
entity->AcceptInput( "Use", this, this, emptyVariant, useType );
}
}
+104
View File
@@ -0,0 +1,104 @@
// NextBotCombatCharacter.h
// Next generation bot system
// Author: Michael Booth, April 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_H_
#define _NEXT_BOT_H_
#include "NextBotInterface.h"
#include "NextBotManager.h"
#ifdef TERROR
#include "player_lagcompensation.h"
#endif
class NextBotCombatCharacter;
struct animevent_t;
extern ConVar NextBotStop;
//----------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------
/**
* A Next Bot derived from CBaseCombatCharacter
*/
class NextBotCombatCharacter : public CBaseCombatCharacter, public INextBot
{
public:
DECLARE_CLASS( NextBotCombatCharacter, CBaseCombatCharacter );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
NextBotCombatCharacter( void );
virtual ~NextBotCombatCharacter() { }
virtual void Spawn( void );
virtual Vector EyePosition( void );
virtual INextBot *MyNextBotPointer( void ) { return this; }
// Event hooks into NextBot system ---------------------------------------
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual int OnTakeDamage_Dying( const CTakeDamageInfo &info );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void HandleAnimEvent( animevent_t *event );
virtual void OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea ); // invoked (by UpdateLastKnownArea) when we enter a new nav area (or it is reset to NULL)
virtual void Touch( CBaseEntity *other );
virtual void SetModel( const char *szModelName );
virtual void Ignite( float flFlameLifetime, bool bNPCOnly = true, float flSize = 0.0f, bool bCalledByLevelDesigner = false );
virtual void Ignite( float flFlameLifetime, CBaseEntity *pAttacker );
//------------------------------------------------------------------------
virtual bool IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps = 0 );
void UseEntity( CBaseEntity *entity, USE_TYPE useType = USE_TOGGLE );
// Implement this if you use MOVETYPE_CUSTOM
virtual void PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity );
virtual bool BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector );
// hook to INextBot update
void DoThink( void );
// expose to public
int GetLastHitGroup( void ) const; // where on our body were we injured last
virtual bool IsAreaTraversable( const CNavArea *area ) const; // return true if we can use the given area
virtual CBaseCombatCharacter *GetLastAttacker( void ) const; // return the character who last attacked me
// begin INextBot public interface ----------------------------------------------------------------
virtual NextBotCombatCharacter *GetEntity( void ) const { return const_cast< NextBotCombatCharacter * >( this ); }
virtual NextBotCombatCharacter *GetNextBotCombatCharacter( void ) const { return const_cast< NextBotCombatCharacter * >( this ); }
private:
EHANDLE m_lastAttacker;
bool m_didModelChange;
};
inline CBaseCombatCharacter *NextBotCombatCharacter::GetLastAttacker( void ) const
{
return ( m_lastAttacker.Get() == NULL ) ? NULL : m_lastAttacker->MyCombatCharacterPointer();
}
inline int NextBotCombatCharacter::GetLastHitGroup( void ) const
{
return LastHitGroup();
}
//-----------------------------------------------------------------------------------------------------
class NextBotDestroyer
{
public:
NextBotDestroyer( int team );
bool operator() ( INextBot *bot );
int m_team; // the team to delete bots from, or TEAM_ANY for any team
};
#endif // _NEXT_BOT_H_
@@ -0,0 +1,162 @@
// NextBotAttentionInterface.cpp
// Manage what this bot pays attention to
// Author: Michael Booth, April 2007
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "NextBot.h"
#include "NextBotAttentionInterface.h"
#include "NextBotBodyInterface.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//------------------------------------------------------------------------------------------
/**
* Reset to initial state
*/
void IAttention::Reset( void )
{
m_body = GetBot()->GetBodyInterface();
m_attentionSet.RemoveAll();
}
//------------------------------------------------------------------------------------------
/**
* Update internal state
*/
void IAttention::Update( void )
{
}
//------------------------------------------------------------------------------------------
void IAttention::AttendTo( const CBaseCombatCharacter *who, const char *reason )
{
if ( !IsAwareOf( who ) )
{
PointOfInterest p;
p.m_type = PointOfInterest::WHO;
p.m_who = who;
p.m_duration.Start();
m_attentionSet.AddToTail( p );
}
}
//------------------------------------------------------------------------------------------
void IAttention::AttendTo( const CBaseEntity *what, const char *reason )
{
if ( !IsAwareOf( what ) )
{
PointOfInterest p;
p.m_type = PointOfInterest::WHAT;
p.m_what = what;
p.m_duration.Start();
m_attentionSet.AddToTail( p );
}
}
//------------------------------------------------------------------------------------------
void IAttention::AttendTo( const Vector &where, IAttention::SignificanceLevel significance, const char *reason )
{
PointOfInterest p;
p.m_type = PointOfInterest::WHERE;
p.m_where = where;
p.m_duration.Start();
m_attentionSet.AddToTail( p );
}
//------------------------------------------------------------------------------------------
void IAttention::Disregard( const CBaseCombatCharacter *who, const char *reason )
{
FOR_EACH_VEC( m_attentionSet, it )
{
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHO )
{
CBaseCombatCharacter *myWho = m_attentionSet[ it ].m_who;
if ( !myWho || myWho->entindex() == who->entindex() )
{
m_attentionSet.Remove( it );
return;
}
}
}
}
//------------------------------------------------------------------------------------------
void IAttention::Disregard( const CBaseEntity *what, const char *reason )
{
FOR_EACH_VEC( m_attentionSet, it )
{
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHAT )
{
CBaseCombatCharacter *myWhat = m_attentionSet[ it ].m_what;
if ( !myWhat || myWhat->entindex() == what->entindex() )
{
m_attentionSet.Remove( it );
return;
}
}
}
}
//------------------------------------------------------------------------------------------
/**
* Return true if given actor is in our attending set
*/
bool IAttention::IsAwareOf( const CBaseCombatCharacter *who ) const
{
FOR_EACH_VEC( m_attentionSet, it )
{
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHO )
{
CBaseCombatCharacter *myWho = m_attentionSet[ it ].m_who;
if ( myWho && myWho->entindex() == who->entindex() )
{
return true;
}
}
}
return false;
}
//------------------------------------------------------------------------------------------
/**
* Return true if given object is in our attending set
*/
bool IAttention::IsAwareOf( const CBaseEntity *what ) const
{
FOR_EACH_VEC( m_attentionSet, it )
{
if ( m_attentionSet[ it ].m_type == PointOfInterest::WHAT )
{
CBaseEntity *myWhat = m_attentionSet[ it ].m_what;
if ( myWhat && myWhat->entindex() == what->entindex() )
{
return true;
}
}
}
return false;
}
@@ -0,0 +1,81 @@
// NextBotAttentionInterface.h
// Manage what this bot pays attention to
// Author: Michael Booth, April 2007
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_ATTENTION_INTERFACE_H_
#define _NEXT_BOT_ATTENTION_INTERFACE_H_
#include "NextBotComponentInterface.h"
class INextBot;
class IBody;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for managing what a bot pays attention to.
* Vision determines what see see and notice -> Attention determines which of those things we look at -> Low level head/aiming simulation actually moves our head/eyes
*/
class IAttention : public INextBotComponent
{
public:
IAttention( INextBot *bot ) : INextBotComponent( bot ) { }
virtual ~IAttention() { }
virtual void Reset( void ) { } // reset to initial state
virtual void Update( void ) { } // update internal state
enum SignificanceLevel
{
BORING, // background noise
INTERESTING, // notably interesting
COMPELLING, // very hard to pay attention to anything else
IRRESISTIBLE, // can't look away
};
// override these to control the significance of entities in a context-specific way
virtual int CompareSignificance( const CBaseEntity *a, const CBaseEntity *b ) const; // returns <0 if a < b, 0 if a==b, or >0 if a>b
// bring things to our attention
virtual void AttendTo( CBaseEntity *what, const char *reason = NULL );
virtual void AttendTo( const Vector &where, SignificanceLevel significance, const char *reason = NULL );
// remove things from our attention
virtual void Disregard( CBaseEntity *what, const char *reason = NULL );
virtual bool IsAwareOf( CBaseEntity *what ) const; // return true if given object is in our attending set
virtual float GetAwareDuration( CBaseEntity *what ) const; // return how long we've been aware of this entity
// INextBotEventResponder ------------------------------------------------------------------
virtual void OnInjured( const CTakeDamageInfo &info ); // when bot is damaged by something
virtual void OnContact( CBaseEntity *other, CGameTrace *result = NULL ); // invoked when bot touches 'other'
virtual void OnSight( CBaseEntity *subject ); // when subject initially enters bot's visual awareness
virtual void OnLostSight( CBaseEntity *subject ); // when subject leaves enters bot's visual awareness
virtual void OnSound( CBaseEntity *source, const CSoundParameters &params ); // when an entity emits a sound
private:
IBody *m_body; // to access head aiming
struct PointOfInterest
{
enum { ENTITY, POSITION } m_type;
CHandle< CBaseEntity > m_entity;
Vector m_position;
IntervalTimer m_duration; // how long has this PoI been in our attention set
};
CUtlVector< PointOfInterest > m_attentionSet; // the set of things we are attending to
};
inline int IAttention::CompareSignificance( const CBaseEntity *a, const CBaseEntity *b ) const
{
return 0;
}
#endif // _NEXT_BOT_ATTENTION_INTERFACE_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
// NextBotBodyInterface.cpp
// Control and information about the bot's body state (posture, animation state, etc)
// Author: Michael Booth, April 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "NextBot.h"
#include "NextBotBodyInterface.h"
void IBody::AimHeadTowards( const Vector &lookAtPos, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
{
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::FAILED );
}
}
void IBody::AimHeadTowards( CBaseEntity *subject, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
{
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::FAILED );
}
}
bool IBody::SetPosition( const Vector &pos )
{
GetBot()->GetEntity()->SetAbsOrigin( pos );
return true;
}
const Vector &IBody::GetEyePosition( void ) const
{
static Vector eye;
eye = GetBot()->GetEntity()->WorldSpaceCenter();
return eye;
}
const Vector &IBody::GetViewVector( void ) const
{
static Vector view;
AngleVectors( GetBot()->GetEntity()->EyeAngles(), &view );
return view;
}
bool IBody::IsHeadAimingOnTarget( void ) const
{
return false;
}
+311
View File
@@ -0,0 +1,311 @@
// NextBotBodyInterface.h
// Control and information about the bot's body state (posture, animation state, etc)
// Author: Michael Booth, April 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_BODY_INTERFACE_H_
#define _NEXT_BOT_BODY_INTERFACE_H_
#include "animation.h"
#include "NextBotComponentInterface.h"
class INextBot;
struct animevent_t;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for control and information about the bot's body state (posture, animation state, etc)
*/
class IBody : public INextBotComponent
{
public:
IBody( INextBot *bot ) : INextBotComponent( bot ) { }
virtual ~IBody() { }
virtual void Reset( void ) { INextBotComponent::Reset(); } // reset to initial state
virtual void Update( void ) { } // update internal state
/**
* Move the bot to a new position.
* If the body is not currently movable or if it
* is in a motion-controlled animation activity
* the position will not be changed and false will be returned.
*/
virtual bool SetPosition( const Vector &pos );
virtual const Vector &GetEyePosition( void ) const; // return the eye position of the bot in world coordinates
virtual const Vector &GetViewVector( void ) const; // return the view unit direction vector in world coordinates
enum LookAtPriorityType
{
BORING,
INTERESTING, // last known enemy location, dangerous sound location
IMPORTANT, // a danger
CRITICAL, // an active threat to our safety
MANDATORY // nothing can interrupt this look at - two simultaneous look ats with this priority is an error
};
virtual void AimHeadTowards( const Vector &lookAtPos,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // aim the bot's head towards the given goal
virtual void AimHeadTowards( CBaseEntity *subject,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // continually aim the bot's head towards the given subject
virtual bool IsHeadAimingOnTarget( void ) const; // return true if the bot's head has achieved its most recent lookat target
virtual bool IsHeadSteady( void ) const; // return true if head is not rapidly turning to look somewhere else
virtual float GetHeadSteadyDuration( void ) const; // return the duration that the bot's head has not been rotating
virtual float GetHeadAimSubjectLeadTime( void ) const; // return how far into the future we should predict our moving subject's position to aim at when tracking subject look-ats
virtual float GetMaxHeadAngularVelocity( void ) const; // return max turn rate of head in degrees/second
enum ActivityType
{
MOTION_CONTROLLED_XY = 0x0001, // XY position and orientation of the bot is driven by the animation.
MOTION_CONTROLLED_Z = 0x0002, // Z position of the bot is driven by the animation.
ACTIVITY_UNINTERRUPTIBLE= 0x0004, // activity can't be changed until animation finishes
ACTIVITY_TRANSITORY = 0x0008, // a short animation that takes over from the underlying animation momentarily, resuming it upon completion
ENTINDEX_PLAYBACK_RATE = 0x0010, // played back at different rates based on entindex
};
/**
* Begin an animation activity, return false if we cant do that right now.
*/
virtual bool StartActivity( Activity act, unsigned int flags = 0 );
virtual int SelectAnimationSequence( Activity act ) const; // given an Activity, select and return a specific animation sequence within it
virtual Activity GetActivity( void ) const; // return currently animating activity
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
virtual bool HasActivityType( unsigned int flags ) const; // return true if currently animating activity has any of the given flags
enum PostureType
{
STAND,
CROUCH,
SIT,
CRAWL,
LIE
};
virtual void SetDesiredPosture( PostureType posture ) { } // request a posture change
virtual PostureType GetDesiredPosture( void ) const; // get posture body is trying to assume
virtual bool IsDesiredPosture( PostureType posture ) const; // return true if body is trying to assume this posture
virtual bool IsInDesiredPosture( void ) const; // return true if body's actual posture matches its desired posture
virtual PostureType GetActualPosture( void ) const; // return body's current actual posture
virtual bool IsActualPosture( PostureType posture ) const; // return true if body is actually in the given posture
virtual bool IsPostureMobile( void ) const; // return true if body's current posture allows it to move around the world
virtual bool IsPostureChanging( void ) const; // return true if body's posture is in the process of changing to new posture
/**
* "Arousal" is the level of excitedness/arousal/anxiety of the body.
* Is changes instantaneously to avoid complex interactions with posture transitions.
*/
enum ArousalType
{
NEUTRAL,
ALERT,
INTENSE
};
virtual void SetArousal( ArousalType arousal ) { } // arousal level change
virtual ArousalType GetArousal( void ) const; // get arousal level
virtual bool IsArousal( ArousalType arousal ) const; // return true if body is at this arousal level
virtual float GetHullWidth( void ) const; // width of bot's collision hull in XY plane
virtual float GetHullHeight( void ) const; // height of bot's current collision hull based on posture
virtual float GetStandHullHeight( void ) const; // height of bot's collision hull when standing
virtual float GetCrouchHullHeight( void ) const; // height of bot's collision hull when crouched
virtual const Vector &GetHullMins( void ) const; // return current collision hull minimums based on actual body posture
virtual const Vector &GetHullMaxs( void ) const; // return current collision hull maximums based on actual body posture
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
};
inline bool IBody::IsHeadSteady( void ) const
{
return true;
}
inline float IBody::GetHeadSteadyDuration( void ) const
{
return 0.0f;
}
inline float IBody::GetHeadAimSubjectLeadTime( void ) const
{
return 0.0f;
}
inline float IBody::GetMaxHeadAngularVelocity( void ) const
{
return 100.0f;
}
inline bool IBody::StartActivity( Activity act, unsigned int flags )
{
return false;
}
inline int IBody::SelectAnimationSequence( Activity act ) const
{
return 0;
}
inline Activity IBody::GetActivity( void ) const
{
return ACT_INVALID;
}
inline bool IBody::IsActivity( Activity act ) const
{
return false;
}
inline bool IBody::HasActivityType( unsigned int flags ) const
{
return false;
}
inline IBody::PostureType IBody::GetDesiredPosture( void ) const
{
return IBody::STAND;
}
inline bool IBody::IsDesiredPosture( PostureType posture ) const
{
return true;
}
inline bool IBody::IsInDesiredPosture( void ) const
{
return true;
}
inline IBody::PostureType IBody::GetActualPosture( void ) const
{
return IBody::STAND;
}
inline bool IBody::IsActualPosture( PostureType posture ) const
{
return true;
}
inline bool IBody::IsPostureMobile( void ) const
{
return true;
}
inline bool IBody::IsPostureChanging( void ) const
{
return false;
}
inline IBody::ArousalType IBody::GetArousal( void ) const
{
return IBody::NEUTRAL;
}
inline bool IBody::IsArousal( ArousalType arousal ) const
{
return true;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Width of bot's collision hull in XY plane
*/
inline float IBody::GetHullWidth( void ) const
{
return 26.0f;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Height of bot's current collision hull based on posture
*/
inline float IBody::GetHullHeight( void ) const
{
switch( GetActualPosture() )
{
case LIE:
return 16.0f;
case SIT:
case CROUCH:
return GetCrouchHullHeight();
case STAND:
default:
return GetStandHullHeight();
}
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Height of bot's collision hull when standing
*/
inline float IBody::GetStandHullHeight( void ) const
{
return 68.0f;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Height of bot's collision hull when crouched
*/
inline float IBody::GetCrouchHullHeight( void ) const
{
return 32.0f;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Return current collision hull minimums based on actual body posture
*/
inline const Vector &IBody::GetHullMins( void ) const
{
static Vector hullMins;
hullMins.x = -GetHullWidth()/2.0f;
hullMins.y = hullMins.x;
hullMins.z = 0.0f;
return hullMins;
}
//---------------------------------------------------------------------------------------------------------------------------
/**
* Return current collision hull maximums based on actual body posture
*/
inline const Vector &IBody::GetHullMaxs( void ) const
{
static Vector hullMaxs;
hullMaxs.x = GetHullWidth()/2.0f;
hullMaxs.y = hullMaxs.x;
hullMaxs.z = GetHullHeight();
return hullMaxs;
}
inline unsigned int IBody::GetSolidMask( void ) const
{
return MASK_NPCSOLID;
}
#endif // _NEXT_BOT_BODY_INTERFACE_H_
@@ -0,0 +1,24 @@
// NextBotComponentInterface.cpp
// Implentation of system methods for NextBot component interface
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "NextBotInterface.h"
#include "NextBotComponentInterface.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
INextBotComponent::INextBotComponent( INextBot *bot )
{
m_curInterval = TICK_INTERVAL;
m_lastUpdateTime = 0;
m_bot = bot;
// register this component with the bot
bot->RegisterComponent( this );
}
@@ -0,0 +1,98 @@
// NextBotComponentInterface.h
// Interface for all components
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_COMPONENT_INTERFACE_H_
#define _NEXT_BOT_COMPONENT_INTERFACE_H_
#include "NextBotEventResponderInterface.h"
class INextBot;
class Path;
class CGameTrace;
class CTakeDamageInfo;
//--------------------------------------------------------------------------------------------------------------------------
/**
* Various processes can invoke a "reply" (ie: callback) via instances of this interface
*/
class INextBotReply
{
public:
virtual void OnSuccess( INextBot *bot ) { } // invoked when process completed successfully
enum FailureReason
{
DENIED,
INTERRUPTED,
FAILED
};
virtual void OnFail( INextBot *bot, FailureReason reason ) { } // invoked when process failed
};
//--------------------------------------------------------------------------------------------------------------------------
/**
* Next Bot component interface
*/
class INextBotComponent : public INextBotEventResponder
{
public:
INextBotComponent( INextBot *bot );
virtual ~INextBotComponent() { }
virtual void Reset( void ) { m_lastUpdateTime = 0; m_curInterval = TICK_INTERVAL; } // reset to initial state
virtual void Update( void ) = 0; // update internal state
virtual void Upkeep( void ) { } // lightweight update guaranteed to occur every server tick
inline bool ComputeUpdateInterval(); // return false is no time has elapsed (interval is zero)
inline float GetUpdateInterval();
virtual INextBot *GetBot( void ) const { return m_bot; }
private:
float m_lastUpdateTime;
float m_curInterval;
friend class INextBot;
INextBot *m_bot;
INextBotComponent *m_nextComponent; // simple linked list of components in the bot
};
inline bool INextBotComponent::ComputeUpdateInterval()
{
if ( m_lastUpdateTime )
{
float interval = gpGlobals->curtime - m_lastUpdateTime;
const float minInterval = 0.0001f;
if ( interval > minInterval )
{
m_curInterval = interval;
m_lastUpdateTime = gpGlobals->curtime;
return true;
}
return false;
}
// First update - assume a reasonable interval.
// We need the very first update to do work, for cases
// where the bot was just created and we need to propagate
// an event to it immediately.
m_curInterval = 0.033f;
m_lastUpdateTime = gpGlobals->curtime - m_curInterval;
return true;
}
inline float INextBotComponent::GetUpdateInterval()
{
return m_curInterval;
}
#endif // _NEXT_BOT_COMPONENT_INTERFACE_H_
@@ -0,0 +1,96 @@
// NextBotContextualQueryInterface.h
// Queries within the context of the bot's current behavior state
// Author: Michael Booth, June 2007
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_CONTEXTUAL_QUERY_H_
#define _NEXT_BOT_CONTEXTUAL_QUERY_H_
class INextBot;
class CBaseEntity;
class CBaseCombatCharacter;
class Path;
class CKnownEntity;
/**
* Since behaviors can have several concurrent actions active, we ask
* the topmost child action first, and if it defers, its parent, and so
* on, until we get a definitive answer.
*/
enum QueryResultType
{
ANSWER_NO,
ANSWER_YES,
ANSWER_UNDEFINED
};
// Can pass this into IContextualQuery::IsHindrance to see if any hindrance is ever possible
#define IS_ANY_HINDRANCE_POSSIBLE ( (CBaseEntity*)0xFFFFFFFF )
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for queries that are dependent on the bot's current behavior state
*/
class IContextualQuery
{
public:
virtual ~IContextualQuery() { }
virtual QueryResultType ShouldPickUp( const INextBot *me, CBaseEntity *item ) const; // if the desired item was available right now, should we pick it up?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // return true if we should wait for 'blocker' that is across our path somewhere up ahead.
virtual Vector SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const; // given a subject, return the world space position we should aim at
/**
* Allow bot to approve of positions game movement tries to put him into.
* This is most useful for bots derived from CBasePlayer that go through
* the player movement system.
*/
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const;
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
const CBaseCombatCharacter *subject,
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats to 'subject', or NULL if we have no opinion
};
inline QueryResultType IContextualQuery::ShouldPickUp( const INextBot *me, CBaseEntity *item ) const
{
return ANSWER_UNDEFINED;
}
inline QueryResultType IContextualQuery::ShouldHurry( const INextBot *me ) const
{
return ANSWER_UNDEFINED;
}
inline QueryResultType IContextualQuery::ShouldRetreat( const INextBot *me ) const
{
return ANSWER_UNDEFINED;
}
inline QueryResultType IContextualQuery::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
{
return ANSWER_UNDEFINED;
}
inline Vector IContextualQuery::SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const
{
return vec3_origin;
}
inline QueryResultType IContextualQuery::IsPositionAllowed( const INextBot *me, const Vector &pos ) const
{
return ANSWER_UNDEFINED;
}
inline const CKnownEntity *IContextualQuery::SelectMoreDangerousThreat( const INextBot *me, const CBaseCombatCharacter *subject, const CKnownEntity *threat1, const CKnownEntity *threat2 ) const
{
return NULL;
}
#endif // _NEXT_BOT_CONTEXTUAL_QUERY_H_
+22
View File
@@ -0,0 +1,22 @@
#ifndef NEXTBOT_DEBUG_H
#define NEXTBOT_DEBUG_H
//------------------------------------------------------------------------------
// Debug flags for nextbot
enum NextBotDebugType
{
NEXTBOT_DEBUG_NONE = 0,
NEXTBOT_BEHAVIOR = 0x0001,
NEXTBOT_LOOK_AT = 0x0002,
NEXTBOT_PATH = 0x0004,
NEXTBOT_ANIMATION = 0x0008,
NEXTBOT_LOCOMOTION = 0x0010,
NEXTBOT_VISION = 0x0020,
NEXTBOT_HEARING = 0x0040,
NEXTBOT_EVENTS = 0x0080,
NEXTBOT_ERRORS = 0x0100, // when things go wrong, like being stuck
NEXTBOT_DEBUG_ALL = 0xFFFF
};
#endif
@@ -0,0 +1,541 @@
// NextBotEventResponderInterface.h
// Interface for propagating and responding to events
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
#define _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
class Path;
class CTakeDamageInfo;
class CBaseEntity;
class CDOTABaseAbility;
struct CSoundParameters;
struct animevent_t;
#include "ai_speech.h"
//--------------------------------------------------------------------------------------------------------------------------
enum MoveToFailureType
{
FAIL_NO_PATH_EXISTS,
FAIL_STUCK,
FAIL_FELL_OFF,
};
//--------------------------------------------------------------------------------------------------------------------------
/**
* Events propagated to/between components.
* To add an event, add its signature here and implement its propagation
* to derived classes via FirstContainedResponder() and NextContainedResponder().
* NOTE: Also add a translator to the Action class in NextBotBehavior.h.
*/
class INextBotEventResponder
{
public:
DECLARE_CLASS_NOBASE( INextBotEventResponder );
virtual ~INextBotEventResponder() { }
// these methods are used by derived classes to define how events propagate
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return NULL; }
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; }
//
// Events. All events must be 'extended' by calling the derived class explicitly to ensure propagation.
// Each event must implement its propagation in this interface class.
//
virtual void OnLeaveGround( CBaseEntity *ground ); // invoked when bot leaves ground for any reason
virtual void OnLandOnGround( CBaseEntity *ground ); // invoked when bot lands on the ground after being in the air
virtual void OnContact( CBaseEntity *other, CGameTrace *result = NULL ); // invoked when bot touches 'other'
virtual void OnMoveToSuccess( const Path *path ); // invoked when a bot reaches the end of the given Path
virtual void OnMoveToFailure( const Path *path, MoveToFailureType reason ); // invoked when a bot fails to reach the end of the given Path
virtual void OnStuck( void ); // invoked when bot becomes stuck while trying to move
virtual void OnUnStuck( void ); // invoked when a previously stuck bot becomes un-stuck and can again move
virtual void OnPostureChanged( void ); // when bot has assumed new posture (query IBody for posture)
virtual void OnAnimationActivityComplete( int activity ); // when animation activity has finished playing
virtual void OnAnimationActivityInterrupted( int activity );// when animation activity was replaced by another animation
virtual void OnAnimationEvent( animevent_t *event ); // when a QC-file animation event is triggered by the current animation sequence
virtual void OnIgnite( void ); // when bot starts to burn
virtual void OnInjured( const CTakeDamageInfo &info ); // when bot is damaged by something
virtual void OnKilled( const CTakeDamageInfo &info ); // when the bot's health reaches zero
virtual void OnOtherKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info ); // when someone else dies
virtual void OnSight( CBaseEntity *subject ); // when subject initially enters bot's visual awareness
virtual void OnLostSight( CBaseEntity *subject ); // when subject leaves enters bot's visual awareness
virtual void OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys ); // when an entity emits a sound. "pos" is world coordinates of sound. "keys" are from sound's GameData
virtual void OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ); // when an Actor speaks a concept
virtual void OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ); // when someone fires a weapon
virtual void OnNavAreaChanged( CNavArea *newArea, CNavArea *oldArea ); // when bot enters a new navigation area
virtual void OnModelChanged( void ); // when the entity's model has been changed
virtual void OnPickUp( CBaseEntity *item, CBaseCombatCharacter *giver ); // when something is added to our inventory
virtual void OnDrop( CBaseEntity *item ); // when something is removed from our inventory
virtual void OnCommandAttack( CBaseEntity *victim ); // attack the given entity
virtual void OnCommandApproach( const Vector &pos, float range = 0.0f ); // move to within range of the given position
virtual void OnCommandApproach( CBaseEntity *goal ); // follow the given leader
virtual void OnCommandRetreat( CBaseEntity *threat, float range = 0.0f ); // retreat from the threat at least range units away (0 == infinite)
virtual void OnCommandPause( float duration = 0.0f ); // pause for the given duration (0 == forever)
virtual void OnCommandResume( void ); // resume after a pause
virtual void OnCommandString( const char *command ); // for debugging: respond to an arbitrary string representing a generalized command
virtual void OnShoved( CBaseEntity *pusher ); // 'pusher' has shoved me
virtual void OnBlinded( CBaseEntity *blinder ); // 'blinder' has blinded me with a flash of light
virtual void OnTerritoryContested( int territoryID ); // territory has been invaded and is changing ownership
virtual void OnTerritoryCaptured( int territoryID ); // we have captured enemy territory
virtual void OnTerritoryLost( int territoryID ); // we have lost territory to the enemy
virtual void OnWin( void );
virtual void OnLose( void );
#ifdef DOTA_SERVER_DLL
virtual void OnCommandMoveTo( const Vector &pos );
virtual void OnCommandMoveToAggressive( const Vector &pos );
virtual void OnCommandAttack( CBaseEntity *victim, bool bDeny );
virtual void OnCastAbilityNoTarget( CDOTABaseAbility *ability );
virtual void OnCastAbilityOnPosition( CDOTABaseAbility *ability, const Vector &pos );
virtual void OnCastAbilityOnTarget( CDOTABaseAbility *ability, CBaseEntity *target );
virtual void OnDropItem( const Vector &pos, CBaseEntity *item );
virtual void OnPickupItem( CBaseEntity *item );
virtual void OnPickupRune( CBaseEntity *item );
virtual void OnStop();
virtual void OnFriendThreatened( CBaseEntity *friendly, CBaseEntity *threat );
virtual void OnCancelAttack( CBaseEntity *pTarget );
virtual void OnDominated();
virtual void OnWarped( Vector vStartPos );
#endif
};
inline void INextBotEventResponder::OnLeaveGround( CBaseEntity *ground )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnLeaveGround( ground );
}
}
inline void INextBotEventResponder::OnLandOnGround( CBaseEntity *ground )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnLandOnGround( ground );
}
}
inline void INextBotEventResponder::OnContact( CBaseEntity *other, CGameTrace *result )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnContact( other, result );
}
}
inline void INextBotEventResponder::OnMoveToSuccess( const Path *path )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnMoveToSuccess( path );
}
}
inline void INextBotEventResponder::OnMoveToFailure( const Path *path, MoveToFailureType reason )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnMoveToFailure( path, reason );
}
}
inline void INextBotEventResponder::OnStuck( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnStuck();
}
}
inline void INextBotEventResponder::OnUnStuck( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnUnStuck();
}
}
inline void INextBotEventResponder::OnPostureChanged( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnPostureChanged();
}
}
inline void INextBotEventResponder::OnAnimationActivityComplete( int activity )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnAnimationActivityComplete( activity );
}
}
inline void INextBotEventResponder::OnAnimationActivityInterrupted( int activity )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnAnimationActivityInterrupted( activity );
}
}
inline void INextBotEventResponder::OnAnimationEvent( animevent_t *event )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnAnimationEvent( event );
}
}
inline void INextBotEventResponder::OnIgnite( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnIgnite();
}
}
inline void INextBotEventResponder::OnInjured( const CTakeDamageInfo &info )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnInjured( info );
}
}
inline void INextBotEventResponder::OnKilled( const CTakeDamageInfo &info )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnKilled( info );
}
}
inline void INextBotEventResponder::OnOtherKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnOtherKilled( victim, info );
}
}
inline void INextBotEventResponder::OnSight( CBaseEntity *subject )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnSight( subject );
}
}
inline void INextBotEventResponder::OnLostSight( CBaseEntity *subject )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnLostSight( subject );
}
}
inline void INextBotEventResponder::OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnSound( source, pos, keys );
}
}
inline void INextBotEventResponder::OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnSpokeConcept( who, concept, response );
}
}
inline void INextBotEventResponder::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnWeaponFired( whoFired, weapon );
}
}
inline void INextBotEventResponder::OnNavAreaChanged( CNavArea *newArea, CNavArea *oldArea )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnNavAreaChanged( newArea, oldArea );
}
}
inline void INextBotEventResponder::OnModelChanged( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnModelChanged();
}
}
inline void INextBotEventResponder::OnPickUp( CBaseEntity *item, CBaseCombatCharacter *giver )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnPickUp( item, giver );
}
}
inline void INextBotEventResponder::OnDrop( CBaseEntity *item )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnDrop( item );
}
}
inline void INextBotEventResponder::OnShoved( CBaseEntity *pusher )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnShoved( pusher );
}
}
inline void INextBotEventResponder::OnBlinded( CBaseEntity *blinder )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnBlinded( blinder );
}
}
inline void INextBotEventResponder::OnCommandAttack( CBaseEntity *victim )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandAttack( victim );
}
}
inline void INextBotEventResponder::OnCommandApproach( const Vector &pos, float range )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandApproach( pos, range );
}
}
inline void INextBotEventResponder::OnCommandApproach( CBaseEntity *goal )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandApproach( goal );
}
}
inline void INextBotEventResponder::OnCommandRetreat( CBaseEntity *threat, float range )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandRetreat( threat, range );
}
}
inline void INextBotEventResponder::OnCommandPause( float duration )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandPause( duration );
}
}
inline void INextBotEventResponder::OnCommandResume( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandResume();
}
}
inline void INextBotEventResponder::OnCommandString( const char *command )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandString( command );
}
}
inline void INextBotEventResponder::OnTerritoryContested( int territoryID )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnTerritoryContested( territoryID );
}
}
inline void INextBotEventResponder::OnTerritoryCaptured( int territoryID )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnTerritoryCaptured( territoryID );
}
}
inline void INextBotEventResponder::OnTerritoryLost( int territoryID )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnTerritoryLost( territoryID );
}
}
inline void INextBotEventResponder::OnWin( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnWin();
}
}
inline void INextBotEventResponder::OnLose( void )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnLose();
}
}
#ifdef DOTA_SERVER_DLL
inline void INextBotEventResponder::OnCommandMoveTo( const Vector &pos )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandMoveTo( pos );
}
}
inline void INextBotEventResponder::OnCommandMoveToAggressive( const Vector &pos )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandMoveToAggressive( pos );
}
}
inline void INextBotEventResponder::OnCommandAttack( CBaseEntity *victim, bool bDeny )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCommandAttack( victim, bDeny );
}
}
inline void INextBotEventResponder::OnCastAbilityNoTarget( CDOTABaseAbility *ability )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCastAbilityNoTarget( ability );
}
}
inline void INextBotEventResponder::OnCastAbilityOnPosition( CDOTABaseAbility *ability, const Vector &pos )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCastAbilityOnPosition( ability, pos );
}
}
inline void INextBotEventResponder::OnCastAbilityOnTarget( CDOTABaseAbility *ability, CBaseEntity *target )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCastAbilityOnTarget( ability, target );
}
}
inline void INextBotEventResponder::OnDropItem( const Vector &pos, CBaseEntity *item )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnDropItem( pos, item );
}
}
inline void INextBotEventResponder::OnPickupItem( CBaseEntity *item )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnPickupItem( item );
}
}
inline void INextBotEventResponder::OnPickupRune( CBaseEntity *item )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnPickupRune( item );
}
}
inline void INextBotEventResponder::OnStop()
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnStop();
}
}
inline void INextBotEventResponder::OnFriendThreatened( CBaseEntity *friendly, CBaseEntity *threat )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnFriendThreatened( friendly, threat );
}
}
inline void INextBotEventResponder::OnCancelAttack( CBaseEntity *pTarget )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnCancelAttack( pTarget );
}
}
inline void INextBotEventResponder::OnDominated()
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnDominated();
}
}
inline void INextBotEventResponder::OnWarped( Vector vStartPos )
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
sub->OnWarped( vStartPos );
}
}
#endif
#endif // _NEXT_BOT_EVENT_RESPONDER_INTERFACE_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,273 @@
// NextBotGroundLocomotion.h
// Basic ground-based movement for NextBotCombatCharacters
// Author: Michael Booth, February 2009
// Note: This is a refactoring of ZombieBotLocomotion from L4D
#ifndef NEXT_BOT_GROUND_LOCOMOTION_H
#define NEXT_BOT_GROUND_LOCOMOTION_H
#include "NextBotLocomotionInterface.h"
#include "nav_mesh.h"
class NextBotCombatCharacter;
//----------------------------------------------------------------------------------------------------------------
/**
* Basic ground-based movement for NextBotCombatCharacters.
* This locomotor resolves collisions and assumes a ground-based bot under the influence of gravity.
*/
class NextBotGroundLocomotion : public ILocomotion
{
public:
DECLARE_CLASS( NextBotGroundLocomotion, ILocomotion );
NextBotGroundLocomotion( INextBot *bot );
virtual ~NextBotGroundLocomotion();
virtual void Reset( void ); // reset locomotor to initial state
virtual void Update( void ); // update internal state
virtual void Approach( const Vector &pos, float goalWeight = 1.0f ); // move directly towards the given position
virtual void DriveTo( const Vector &pos ); // Move the bot to the precise given position immediately,
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ); // initiate a jump to an adjacent high ledge, return false if climb can't start
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ); // initiate a jump across an empty volume of space to far side
virtual void Jump( void ); // initiate a simple undirected jump in the air
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
virtual void Run( void ); // set desired movement speed to running
virtual void Walk( void ); // set desired movement speed to walking
virtual void Stop( void ); // set desired movement speed to stopped
virtual bool IsRunning( void ) const;
virtual void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
virtual float GetSpeedLimit( void ) const; // get maximum speed bot can reach, regardless of desired speed
virtual bool IsOnGround( void ) const; // return true if standing on something
virtual void OnLeaveGround( CBaseEntity *ground ); // invoked when bot leaves ground for any reason
virtual void OnLandOnGround( CBaseEntity *ground ); // invoked when bot lands on the ground after being in the air
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
virtual const Vector &GetGroundNormal( void ) const;// surface normal of the ground we are in contact with
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // climb the given ladder to the top and dismount
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // descend the given ladder to the bottom and dismount
virtual bool IsUsingLadder( void ) const;
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
virtual void SetDesiredLean( const QAngle &lean );
virtual const QAngle &GetDesiredLean( void ) const;
virtual const Vector &GetFeet( void ) const; // return position of "feet" - the driving point where the bot contacts the ground
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
virtual const Vector &GetAcceleration( void ) const; // return current world space acceleration
virtual void SetAcceleration( const Vector &accel ); // set world space acceleration
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
virtual void SetVelocity( const Vector &vel ); // set world space velocity
virtual void OnMoveToSuccess( const Path *path ); // invoked when an bot reaches its MoveTo goal
virtual void OnMoveToFailure( const Path *path, MoveToFailureType reason ); // invoked when an bot fails to reach a MoveTo goal
private:
void UpdatePosition( const Vector &newPos ); // move to newPos, resolving any collisions along the way
void UpdateGroundConstraint( void ); // keep ground solid
Vector ResolveCollisionV0( Vector from, Vector to, int recursionLimit );
Vector ResolveZombieCollisions( const Vector &pos ); // push away zombies that are interpenetrating
Vector ResolveCollision( const Vector &from, const Vector &to, int recursionLimit ); // check for collisions along move
bool DetectCollision( trace_t *pTrace, int &nDestructionAllowed, const Vector &from, const Vector &to, const Vector &vecMins, const Vector &vecMaxs );
void ApplyAccumulatedApproach( void );
bool DidJustJump( void ) const; // return true if we just started a jump
bool TraverseLadder( void ); // return true if we are climbing a ladder
virtual float GetGravity( void ) const; // return gravity force acting on bot
virtual float GetFrictionForward( void ) const; // return magnitude of forward friction
virtual float GetFrictionSideways( void ) const; // return magnitude of lateral friction
virtual float GetMaxYawRate( void ) const; // return max rate of yaw rotation
private:
NextBotCombatCharacter *m_nextBot;
Vector m_priorPos; // last update's position
Vector m_lastValidPos; // last valid position (not interpenetrating)
Vector m_acceleration;
Vector m_velocity;
float m_desiredSpeed; // speed bot wants to be moving
float m_actualSpeed; // actual speed bot is moving
float m_maxRunSpeed;
float m_forwardLean;
float m_sideLean;
QAngle m_desiredLean;
bool m_isJumping; // if true, we have jumped and have not yet hit the ground
bool m_isJumpingAcrossGap; // if true, we have jumped across a gap and have not yet hit the ground
EHANDLE m_ground; // have to manage this ourselves, since MOVETYPE_CUSTOM always NULLs out GetGroundEntity()
Vector m_groundNormal; // surface normal of the ground we are in contact with
bool m_isClimbingUpToLedge; // true if we are jumping up to an adjacent ledge
Vector m_ledgeJumpGoalPos;
bool m_isUsingFullFeetTrace; // true if we're in the air and tracing the lowest StepHeight in ResolveCollision
const CNavLadder *m_ladder; // ladder we are currently climbing/descending
const CNavArea *m_ladderDismountGoal; // the area we enter when finished with our ladder move
bool m_isGoingUpLadder; // if false, we're going down
CountdownTimer m_inhibitObstacleAvoidanceTimer; // when active, turn off path following feelers
CountdownTimer m_wiggleTimer; // for wiggling
NavRelativeDirType m_wiggleDirection;
mutable Vector m_eyePos; // for use with GetEyes(), etc.
Vector m_moveVector; // the direction of our motion in XY plane
float m_moveYaw; // global yaw of movement direction
Vector m_accumApproachVectors; // weighted sum of Approach() calls since last update
float m_accumApproachWeights;
bool m_bRecomputePostureOnCollision;
CountdownTimer m_ignorePhysicsPropTimer; // if active, don't collide with physics props (because we got stuck in one)
EHANDLE m_ignorePhysicsProp; // which prop to ignore
};
inline float NextBotGroundLocomotion::GetGravity( void ) const
{
return 1000.0f;
}
inline float NextBotGroundLocomotion::GetFrictionForward( void ) const
{
return 0.0f;
}
inline float NextBotGroundLocomotion::GetFrictionSideways( void ) const
{
return 3.0f;
}
inline float NextBotGroundLocomotion::GetMaxYawRate( void ) const
{
return 250.0f;
}
inline CBaseEntity *NextBotGroundLocomotion::GetGround( void ) const
{
return m_ground;
}
inline const Vector &NextBotGroundLocomotion::GetGroundNormal( void ) const
{
return m_groundNormal;
}
inline void NextBotGroundLocomotion::SetDesiredLean( const QAngle &lean )
{
m_desiredLean = lean;
}
inline const QAngle &NextBotGroundLocomotion::GetDesiredLean( void ) const
{
return m_desiredLean;
}
inline void NextBotGroundLocomotion::SetDesiredSpeed( float speed )
{
m_desiredSpeed = speed;
}
inline float NextBotGroundLocomotion::GetDesiredSpeed( void ) const
{
return m_desiredSpeed;
}
inline bool NextBotGroundLocomotion::IsClimbingOrJumping( void ) const
{
return m_isJumping;
}
inline bool NextBotGroundLocomotion::IsClimbingUpToLedge( void ) const
{
return m_isClimbingUpToLedge;
}
inline bool NextBotGroundLocomotion::IsJumpingAcrossGap( void ) const
{
return m_isJumpingAcrossGap;
}
inline bool NextBotGroundLocomotion::IsRunning( void ) const
{
/// @todo Rethink interface to distinguish actual state vs desired state (do we want to be running, or are we actually at running speed right now)
return m_actualSpeed > 0.9f * GetRunSpeed();
}
inline float NextBotGroundLocomotion::GetStepHeight( void ) const
{
return 18.0f;
}
inline float NextBotGroundLocomotion::GetMaxJumpHeight( void ) const
{
return 180.0f; // 120.0f; // 84.0f; // 58.0f;
}
inline float NextBotGroundLocomotion::GetDeathDropHeight( void ) const
{
return 200.0f;
}
inline float NextBotGroundLocomotion::GetRunSpeed( void ) const
{
return 150.0f;
}
inline float NextBotGroundLocomotion::GetWalkSpeed( void ) const
{
return 75.0f;
}
inline float NextBotGroundLocomotion::GetMaxAcceleration( void ) const
{
return 500.0f;
}
inline float NextBotGroundLocomotion::GetMaxDeceleration( void ) const
{
return 500.0f;
}
#endif // NEXT_BOT_GROUND_LOCOMOTION_H
@@ -0,0 +1,34 @@
// NextBotHearingInterface.h
// Interface for auditory queries of a bot
// Author: Michael Booth, April 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_HEARING_INTERFACE_H_
#define _NEXT_BOT_HEARING_INTERFACE_H_
#include "NextBotComponentInterface.h"
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for hearing sounds
*/
class IHearing : public INextBotComponent
{
public:
IHearing( INextBot *bot ) : INextBotComponent( bot ) { }
virtual ~IHearing() { }
virtual void Reset( void ); // reset to initial state
virtual void Update( void ); // update internal state
virtual float GetTimeSinceHeard( int team ) const; // return time since we heard any member of the given team
virtual CBaseEntity *GetClosestRecognized( int team = TEAM_ANY ) const; // return the closest recognized entity
virtual int GetRecognizedCount( int team, float rangeLimit = -1.0f ) const; // return the number of actors on the given team visible to us closer than rangeLimit
virtual float GetMaxHearingRange( void ) const; // return maximum distance we can hear
virtual float GetMinRecognizeTime( void ) const; // return HEARING reaction time
};
#endif // _NEXT_BOT_HEARING_INTERFACE_H_
@@ -0,0 +1,79 @@
// NextBotIntentionInterface.cpp
// Interface for intentional thinking
// Author: Michael Booth, November 2007
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "NextBotInterface.h"
#include "NextBotIntentionInterface.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//------------------------------------------------------------------------------------------------------------------------
/**
* Given a subject, return the world space position we should aim at
*/
Vector IIntention::SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
Vector result = query->SelectTargetPoint( me, subject );
if ( result != vec3_origin )
{
return result;
}
}
}
// no answer, use a reasonable position
Vector threatMins, threatMaxs;
subject->CollisionProp()->WorldSpaceAABB( &threatMins, &threatMaxs );
Vector targetPoint = subject->GetAbsOrigin();
targetPoint.z += 0.7f * ( threatMaxs.z - threatMins.z );
return targetPoint;
}
//------------------------------------------------------------------------------------------------------------------------
/**
* Given two threats, decide which one is more dangerous
*/
const CKnownEntity *IIntention::SelectMoreDangerousThreat( const INextBot *me, const CBaseCombatCharacter *subject, const CKnownEntity *threat1, const CKnownEntity *threat2 ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
const CKnownEntity *result = query->SelectMoreDangerousThreat( me, subject, threat1, threat2 );
if ( result )
{
return result;
}
}
}
// no specific decision was made - return closest threat as most dangerous
float range1 = ( subject->GetAbsOrigin() - threat1->GetLastKnownPosition() ).LengthSqr();
float range2 = ( subject->GetAbsOrigin() - threat2->GetLastKnownPosition() ).LengthSqr();
if ( range1 < range2 )
{
return threat1;
}
return threat2;
}
@@ -0,0 +1,191 @@
// NextBotIntentionInterface.h
// Interface for intentional thinking
// Author: Michael Booth, April 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_INTENTION_INTERFACE_H_
#define _NEXT_BOT_INTENTION_INTERFACE_H_
#include "NextBotComponentInterface.h"
#include "NextBotContextualQueryInterface.h"
class INextBot;
//
// Insert this macro in your INextBot-derived class declaration to
// create a IIntention-derived class that handles the bookkeeping
// of instantiating a Behavior with an initial Action and updating it.
//
#define DECLARE_INTENTION_INTERFACE( Actor ) \
\
class Actor##Intention : public IIntention \
{ \
public: \
Actor##Intention( Actor *me ); \
virtual ~Actor##Intention(); \
virtual void Reset( void ); \
virtual void Update( void ); \
virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; } \
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; } \
private: \
Behavior< Actor > *m_behavior; \
}; \
\
public: virtual IIntention *GetIntentionInterface( void ) const { return m_intention; } \
private: Actor##Intention *m_intention; \
public:
//
// Use this macro to create the implementation code for the IIntention-derived class
// declared above. Since this requires InitialAction, it must occur after
// that Action has been declared, so it can be new'd here.
//
#define IMPLEMENT_INTENTION_INTERFACE( Actor, InitialAction ) \
Actor::Actor##Intention::Actor##Intention( Actor *me ) : IIntention( me ) { m_behavior = new Behavior< Actor >( new InitialAction ); } \
Actor::Actor##Intention::~Actor##Intention() { delete m_behavior; } \
void Actor::Actor##Intention::Reset( void ) { delete m_behavior; m_behavior = new Behavior< Actor >( new InitialAction ); } \
void Actor::Actor##Intention::Update( void ) { m_behavior->Update( static_cast< Actor * >( GetBot() ), GetUpdateInterval() ); }
//
// Use this macro in the constructor of your bot to allocate the IIntention-derived class
//
#define ALLOCATE_INTENTION_INTERFACE( Actor ) { m_intention = new Actor##Intention( this ); }
//
// Use this macro in the destructor of your bot to deallocate the IIntention-derived class
//
#define DEALLOCATE_INTENTION_INTERFACE { if ( m_intention ) delete m_intention; }
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for intentional thinking.
* The assumption is that this is a container for one or more concurrent Behaviors.
* The "primary" Behavior is the FirstContainedResponder, and so on.
* IContextualQuery requests are prioritized in contained responder order, such that the first responder
* that returns a definitive answer is accepted. WITHIN a given responder (ie: a Behavior), the deepest child
* Behavior in the active stack is asked first, then its parent, and so on, allowing the most specific active
* Behavior to override the query responses of its more general parent Behaviors.
*/
class IIntention : public INextBotComponent, public IContextualQuery
{
public:
IIntention( INextBot *bot ) : INextBotComponent( bot ) { }
virtual ~IIntention() { }
virtual void Reset( void ) { INextBotComponent::Reset(); } // reset to initial state
virtual void Update( void ) { } // update internal state
// IContextualQuery propagation --------------------------------
virtual QueryResultType ShouldPickUp( const INextBot *me, CBaseEntity *item ) const; // if the desired item was available right now, should we pick it up?
virtual QueryResultType ShouldHurry( const INextBot *me ) const; // are we in a hurry?
virtual QueryResultType ShouldRetreat( const INextBot *me ) const; // is it time to retreat?
virtual QueryResultType IsHindrance( const INextBot *me, CBaseEntity *blocker ) const; // return true if we should wait for 'blocker' that is across our path somewhere up ahead.
virtual Vector SelectTargetPoint( const INextBot *me, const CBaseCombatCharacter *subject ) const; // given a subject, return the world space position we should aim at
virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be?
virtual const CKnownEntity * SelectMoreDangerousThreat( const INextBot *me,
const CBaseCombatCharacter *subject, // the subject of the danger
const CKnownEntity *threat1,
const CKnownEntity *threat2 ) const; // return the more dangerous of the two threats, or NULL if we have no opinion
// NOTE: As further queries are added, update the Behavior class to propagate them
};
inline QueryResultType IIntention::ShouldPickUp( const INextBot *me, CBaseEntity *item ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->ShouldPickUp( me, item );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::ShouldHurry( const INextBot *me ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->ShouldHurry( me );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::ShouldRetreat( const INextBot *me ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->ShouldRetreat( me );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::IsHindrance( const INextBot *me, CBaseEntity *blocker ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->IsHindrance( me, blocker );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
inline QueryResultType IIntention::IsPositionAllowed( const INextBot *me, const Vector &pos ) const
{
for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
{
const IContextualQuery *query = dynamic_cast< const IContextualQuery * >( sub );
if ( query )
{
// return the response of the first responder that gives a definitive answer
QueryResultType result = query->IsPositionAllowed( me, pos );
if ( result != ANSWER_UNDEFINED )
{
return result;
}
}
}
return ANSWER_UNDEFINED;
}
#endif // _NEXT_BOT_INTENTION_INTERFACE_H_
+533
View File
@@ -0,0 +1,533 @@
// NextBotInterface.cpp
// Implentation of system methods for NextBot interface
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "props.h"
#include "fmtstr.h"
#include "team.h"
#include "NextBotInterface.h"
#include "NextBotBodyInterface.h"
#include "NextBotManager.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// development only, off by default for 360
ConVar NextBotDebugHistory( "nb_debug_history", IsX360() ? "0" : "1", FCVAR_CHEAT, "If true, each bot keeps a history of debug output in memory" );
//----------------------------------------------------------------------------------------------------------------
INextBot::INextBot( void ) : m_debugHistory( MAX_NEXTBOT_DEBUG_HISTORY, 0 ) // CUtlVector: grow to max length, alloc 0 initially
{
m_tickLastUpdate = -999;
m_id = -1;
m_componentList = NULL;
m_debugDisplayLine = 0;
m_immobileTimer.Invalidate();
m_immobileCheckTimer.Invalidate();
m_immobileAnchor = vec3_origin;
m_currentPath = NULL;
// register with the manager
m_id = TheNextBots().Register( this );
}
//----------------------------------------------------------------------------------------------------------------
INextBot::~INextBot()
{
ResetDebugHistory();
// tell the manager we're gone
TheNextBots().UnRegister( this );
if ( m_baseLocomotion )
delete m_baseLocomotion;
if ( m_baseBody )
delete m_baseBody;
if ( m_baseIntention )
delete m_baseIntention;
if ( m_baseVision )
delete m_baseVision;
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::Reset( void )
{
m_tickLastUpdate = -999;
m_debugType = 0;
m_debugDisplayLine = 0;
m_immobileTimer.Invalidate();
m_immobileCheckTimer.Invalidate();
m_immobileAnchor = vec3_origin;
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
{
comp->Reset();
}
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::ResetDebugHistory( void )
{
for ( int i=0; i<m_debugHistory.Count(); ++i )
{
delete m_debugHistory[i];
}
m_debugHistory.RemoveAll();
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::BeginUpdate()
{
if ( TheNextBots().ShouldUpdate( this ) )
{
TheNextBots().NotifyBeginUpdate( this );
return true;
}
return false;
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::EndUpdate( void )
{
TheNextBots().NotifyEndUpdate( this );
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::Update( void )
{
VPROF_BUDGET( "INextBot::Update", "NextBot" );
m_debugDisplayLine = 0;
if ( IsDebugging( NEXTBOT_DEBUG_ALL ) )
{
CFmtStr msg;
DisplayDebugText( msg.sprintf( "#%d", GetEntity()->entindex() ) );
}
UpdateImmobileStatus();
// update all components
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
{
if ( comp->ComputeUpdateInterval() )
{
comp->Update();
}
}
}
//----------------------------------------------------------------------------------------------------------------
void INextBot::Upkeep( void )
{
// do upkeep for all components
for( INextBotComponent *comp = m_componentList; comp; comp = comp->m_nextComponent )
{
comp->Upkeep();
}
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::SetPosition( const Vector &pos )
{
IBody *body = GetBodyInterface();
if (body)
{
return body->SetPosition( pos );
}
// fall back to setting raw entity position
GetEntity()->SetAbsOrigin( pos );
return true;
}
//----------------------------------------------------------------------------------------------------------------
const Vector &INextBot::GetPosition( void ) const
{
return const_cast< INextBot * >( this )->GetEntity()->GetAbsOrigin();
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return true if given actor is our enemy
*/
bool INextBot::IsEnemy( const CBaseEntity *them ) const
{
if ( them == NULL )
return false;
// this is not strictly correct, as spectators are not enemies
return const_cast< INextBot * >( this )->GetEntity()->GetTeamNumber() != them->GetTeamNumber();
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return true if given actor is our friend
*/
bool INextBot::IsFriend( const CBaseEntity *them ) const
{
if ( them == NULL )
return false;
return const_cast< INextBot * >( this )->GetEntity()->GetTeamNumber() == them->GetTeamNumber();
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return true if 'them' is actually me
*/
bool INextBot::IsSelf( const CBaseEntity *them ) const
{
if ( them == NULL )
return false;
return const_cast< INextBot * >( this )->GetEntity()->entindex() == them->entindex();
}
//----------------------------------------------------------------------------------------------------------------
/**
* Components call this to register themselves with the bot that contains them
*/
void INextBot::RegisterComponent( INextBotComponent *comp )
{
// add to head of singly linked list
comp->m_nextComponent = m_componentList;
m_componentList = comp;
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsRangeLessThan( CBaseEntity *subject, float range ) const
{
Vector botPos;
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
if ( !bot || !subject )
return 0.0f;
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
return computedRange < range;
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsRangeLessThan( const Vector &pos, float range ) const
{
Vector to = pos - GetPosition();
return to.IsLengthLessThan( range );
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsRangeGreaterThan( CBaseEntity *subject, float range ) const
{
Vector botPos;
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
if ( !bot || !subject )
return 0.0f;
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
return computedRange > range;
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsRangeGreaterThan( const Vector &pos, float range ) const
{
Vector to = pos - GetPosition();
return to.IsLengthGreaterThan( range );
}
//----------------------------------------------------------------------------------------------------------------
float INextBot::GetRangeTo( CBaseEntity *subject ) const
{
Vector botPos;
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
if ( !bot || !subject )
return 0.0f;
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
return computedRange;
}
//----------------------------------------------------------------------------------------------------------------
float INextBot::GetRangeTo( const Vector &pos ) const
{
Vector to = pos - GetPosition();
return to.Length();
}
//----------------------------------------------------------------------------------------------------------------
float INextBot::GetRangeSquaredTo( CBaseEntity *subject ) const
{
Vector botPos;
CBaseEntity *bot = const_cast< INextBot * >( this )->GetEntity();
if ( !bot || !subject )
return 0.0f;
bot->CollisionProp()->CalcNearestPoint( subject->WorldSpaceCenter(), &botPos );
float computedRange = subject->CollisionProp()->CalcDistanceFromPoint( botPos );
return computedRange * computedRange;
}
//----------------------------------------------------------------------------------------------------------------
float INextBot::GetRangeSquaredTo( const Vector &pos ) const
{
Vector to = pos - GetPosition();
return to.LengthSqr();
}
//----------------------------------------------------------------------------------------------------------------
bool INextBot::IsDebugging( unsigned int type ) const
{
if ( TheNextBots().IsDebugging( type ) )
{
return TheNextBots().IsDebugFilterMatch( this );
}
return false;
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return the name of this bot for debugging purposes
*/
const char *INextBot::GetDebugIdentifier( void ) const
{
const int nameSize = 256;
static char name[ nameSize ];
Q_snprintf( name, nameSize, "%s(#%d)", const_cast< INextBot * >( this )->GetEntity()->GetClassname(), const_cast< INextBot * >( this )->GetEntity()->entindex() );
return name;
}
//----------------------------------------------------------------------------------------------------------------
/**
* Return true if we match the given debug symbol
*/
bool INextBot::IsDebugFilterMatch( const char *name ) const
{
// compare debug identifier
if ( !Q_strnicmp( name, GetDebugIdentifier(), Q_strlen( name ) ) )
{
return true;
}
// compare team name
CTeam *team = GetEntity()->GetTeam();
if ( team && !Q_strnicmp( name, team->GetName(), Q_strlen( name ) ) )
{
return true;
}
return false;
}
//----------------------------------------------------------------------------------------------------------------
/**
* There are some things we never want to climb on
*/
bool INextBot::IsAbleToClimbOnto( const CBaseEntity *object ) const
{
if ( object == NULL || !const_cast<CBaseEntity *>(object)->IsAIWalkable() )
{
return false;
}
// never climb onto doors
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "prop_door*" ) || FClassnameIs( const_cast< CBaseEntity * >( object ), "func_door*" ) )
{
return false;
}
// ok to climb on this object
return true;
}
//----------------------------------------------------------------------------------------------------------------
/**
* Can we break this object
*/
bool INextBot::IsAbleToBreak( const CBaseEntity *object ) const
{
if ( object && object->m_takedamage == DAMAGE_YES )
{
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "func_breakable" ) &&
object->GetHealth() )
{
return true;
}
if ( FClassnameIs( const_cast< CBaseEntity * >( object ), "func_breakable_surf" ) )
{
return true;
}
if ( dynamic_cast< const CBreakableProp * >( object ) != NULL )
{
return true;
}
}
return false;
}
//----------------------------------------------------------------------------------------------------------
void INextBot::DisplayDebugText( const char *text ) const
{
const_cast< INextBot * >( this )->GetEntity()->EntityText( m_debugDisplayLine++, text, 0.1 );
}
//--------------------------------------------------------------------------------------------------------
void INextBot::DebugConColorMsg( NextBotDebugType debugType, const Color &color, const char *fmt, ... )
{
bool isDataFormatted = false;
va_list argptr;
char data[ MAX_NEXTBOT_DEBUG_LINE_LENGTH ];
if ( developer.GetBool() && IsDebugging( debugType ) )
{
va_start(argptr, fmt);
Q_vsnprintf(data, sizeof( data ), fmt, argptr);
va_end(argptr);
isDataFormatted = true;
ConColorMsg( color, "%s", data );
}
if ( !NextBotDebugHistory.GetBool() )
{
if ( m_debugHistory.Count() )
{
ResetDebugHistory();
}
return;
}
// Don't bother with event data - it's spammy enough to overshadow everything else.
if ( debugType == NEXTBOT_EVENTS )
return;
if ( !isDataFormatted )
{
va_start(argptr, fmt);
Q_vsnprintf(data, sizeof( data ), fmt, argptr);
va_end(argptr);
isDataFormatted = true;
}
int lastLine = m_debugHistory.Count() - 1;
if ( lastLine >= 0 )
{
NextBotDebugLineType *line = m_debugHistory[lastLine];
if ( line->debugType == debugType && V_strstr( line->data, "\n" ) == NULL )
{
// append onto previous line
V_strncat( line->data, data, MAX_NEXTBOT_DEBUG_LINE_LENGTH );
return;
}
}
// Prune out an old line if needed, keeping a pointer to re-use the memory
NextBotDebugLineType *line = NULL;
if ( m_debugHistory.Count() == MAX_NEXTBOT_DEBUG_HISTORY )
{
line = m_debugHistory[0];
m_debugHistory.Remove( 0 );
}
// Add to debug history
if ( !line )
{
line = new NextBotDebugLineType;
}
line->debugType = debugType;
V_strncpy( line->data, data, MAX_NEXTBOT_DEBUG_LINE_LENGTH );
m_debugHistory.AddToTail( line );
}
//--------------------------------------------------------------------------------------------------------
// build a vector of debug history of the given types
void INextBot::GetDebugHistory( unsigned int type, CUtlVector< const NextBotDebugLineType * > *lines ) const
{
if ( !lines )
return;
lines->RemoveAll();
for ( int i=0; i<m_debugHistory.Count(); ++i )
{
NextBotDebugLineType *line = m_debugHistory[i];
if ( line->debugType & type )
{
lines->AddToTail( line );
}
}
}
//--------------------------------------------------------------------------------------------------------
void INextBot::UpdateImmobileStatus( void )
{
if ( m_immobileCheckTimer.IsElapsed() )
{
m_immobileCheckTimer.Start( 1.0f );
// if we haven't moved farther than this in 1 second, we're immobile
if ( ( GetEntity()->GetAbsOrigin() - m_immobileAnchor ).IsLengthGreaterThan( GetImmobileSpeedThreshold() ) )
{
// moved far enough, not immobile
m_immobileAnchor = GetEntity()->GetAbsOrigin();
m_immobileTimer.Invalidate();
}
else
{
// haven't escaped our anchor - we are immobile
if ( !m_immobileTimer.HasStarted() )
{
m_immobileTimer.Start();
}
}
}
}
+302
View File
@@ -0,0 +1,302 @@
// NextBotInterface.h
// Interface for NextBot
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_INTERFACE_H_
#define _NEXT_BOT_INTERFACE_H_
#include "NextBot/NextBotKnownEntity.h"
#include "NextBotComponentInterface.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotBodyInterface.h"
#include "NextBotIntentionInterface.h"
#include "NextBotVisionInterface.h"
#include "NextBotDebug.h"
class CBaseCombatCharacter;
class PathFollower;
//----------------------------------------------------------------------------------------------------------------
/**
* A general purpose filter interface for various bot systems
*/
class INextBotFilter
{
public:
virtual bool IsSelected( const CBaseEntity *candidate ) const = 0; // return true if this entity passes the filter
};
//----------------------------------------------------------------------------------------------------------------
class INextBot : public INextBotEventResponder
{
public:
INextBot( void );
virtual ~INextBot();
int GetBotId() const;
bool BeginUpdate();
void EndUpdate();
virtual void Reset( void ); // (EXTEND) reset to initial state
virtual void Update( void ); // (EXTEND) update internal state
virtual void Upkeep( void ); // (EXTEND) lightweight update guaranteed to occur every server tick
void FlagForUpdate( bool b = true );
bool IsFlaggedForUpdate();
int GetTickLastUpdate() const;
void SetTickLastUpdate( int );
virtual bool IsRemovedOnReset( void ) const { return true; } // remove this bot when the NextBot manager calls Reset
virtual CBaseCombatCharacter *GetEntity( void ) const = 0;
virtual class NextBotCombatCharacter *GetNextBotCombatCharacter( void ) const { return NULL; }
#ifdef TERROR
virtual class SurvivorBot *MySurvivorBotPointer() const { return NULL; }
#endif
// interfaces are never NULL - return base no-op interfaces at a minimum
virtual ILocomotion * GetLocomotionInterface( void ) const;
virtual IBody * GetBodyInterface( void ) const;
virtual IIntention * GetIntentionInterface( void ) const;
virtual IVision * GetVisionInterface( void ) const;
/**
* Attempt to change the bot's position. Return true if successful.
*/
virtual bool SetPosition( const Vector &pos );
virtual const Vector &GetPosition( void ) const; // get the global position of the bot
/**
* Friend/enemy/neutral queries
*/
virtual bool IsEnemy( const CBaseEntity *them ) const; // return true if given entity is our enemy
virtual bool IsFriend( const CBaseEntity *them ) const; // return true if given entity is our friend
virtual bool IsSelf( const CBaseEntity *them ) const; // return true if 'them' is actually me
/**
* Can we climb onto this entity?
*/
virtual bool IsAbleToClimbOnto( const CBaseEntity *object ) const;
/**
* Can we break this entity?
*/
virtual bool IsAbleToBreak( const CBaseEntity *object ) const;
/**
* Sometimes we want to pass through other NextBots. OnContact() will always
* be invoked, but collision resolution can be skipped if this
* method returns false.
*/
virtual bool IsAbleToBlockMovementOf( const INextBot *botInMotion ) const { return true; }
/**
* Should we ever care about noticing physical contact with this entity?
*/
virtual bool ShouldTouch( const CBaseEntity *object ) const { return true; }
/**
* This immobile system is used to track the global state of "am I actually moving or not".
* The OnStuck() event is only emitted when following a path, and paths can be recomputed, etc.
*/
virtual bool IsImmobile( void ) const; // return true if we haven't moved in awhile
virtual float GetImmobileDuration( void ) const; // how long have we been immobile
virtual void ClearImmobileStatus( void );
virtual float GetImmobileSpeedThreshold( void ) const; // return units/second below which this actor is considered "immobile"
/**
* Get the last PathFollower we followed. This method gives other interfaces a
* single accessor to the most recent Path being followed by the myriad of
* different PathFollowers used in the various behaviors the bot may be doing.
*/
virtual const PathFollower *GetCurrentPath( void ) const;
virtual void SetCurrentPath( const PathFollower *path );
virtual void NotifyPathDestruction( const PathFollower *path ); // this PathFollower is going away, which may or may not be ours
// between distance utility methods
virtual bool IsRangeLessThan( CBaseEntity *subject, float range ) const;
virtual bool IsRangeLessThan( const Vector &pos, float range ) const;
virtual bool IsRangeGreaterThan( CBaseEntity *subject, float range ) const;
virtual bool IsRangeGreaterThan( const Vector &pos, float range ) const;
virtual float GetRangeTo( CBaseEntity *subject ) const;
virtual float GetRangeTo( const Vector &pos ) const;
virtual float GetRangeSquaredTo( CBaseEntity *subject ) const;
virtual float GetRangeSquaredTo( const Vector &pos ) const;
// event propagation
virtual INextBotEventResponder *FirstContainedResponder( void ) const;
virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const;
virtual bool IsDebugging( unsigned int type ) const; // return true if this bot is debugging any of the given types
virtual const char *GetDebugIdentifier( void ) const; // return the name of this bot for debugging purposes
virtual bool IsDebugFilterMatch( const char *name ) const; // return true if we match the given debug symbol
virtual void DisplayDebugText( const char *text ) const; // show a line of text on the bot in the world
void DebugConColorMsg( NextBotDebugType debugType, const Color &color, const char *fmt, ... );
enum {
MAX_NEXTBOT_DEBUG_HISTORY = 100,
MAX_NEXTBOT_DEBUG_LINE_LENGTH = 256,
};
struct NextBotDebugLineType
{
NextBotDebugType debugType;
char data[ MAX_NEXTBOT_DEBUG_LINE_LENGTH ];
};
void GetDebugHistory( unsigned int type, CUtlVector< const NextBotDebugLineType * > *lines ) const; // build a vector of debug history of the given types
//------------------------------------------------------------------------------
private:
friend class INextBotComponent;
void RegisterComponent( INextBotComponent *comp ); // components call this to register themselves with the bot that contains them
INextBotComponent *m_componentList; // the first component
const PathFollower *m_currentPath; // the path we most recently followed
int m_id;
bool m_bFlaggedForUpdate;
int m_tickLastUpdate;
unsigned int m_debugType;
mutable int m_debugDisplayLine;
Vector m_immobileAnchor;
CountdownTimer m_immobileCheckTimer;
IntervalTimer m_immobileTimer;
void UpdateImmobileStatus( void );
mutable ILocomotion *m_baseLocomotion;
mutable IBody *m_baseBody;
mutable IIntention *m_baseIntention;
mutable IVision *m_baseVision;
//mutable IAttention *m_baseAttention;
// Debugging info
void ResetDebugHistory( void );
CUtlVector< NextBotDebugLineType * > m_debugHistory;
};
inline const PathFollower *INextBot::GetCurrentPath( void ) const
{
return m_currentPath;
}
inline void INextBot::SetCurrentPath( const PathFollower *path )
{
m_currentPath = path;
}
inline void INextBot::NotifyPathDestruction( const PathFollower *path )
{
if ( m_currentPath == path )
m_currentPath = NULL;
}
inline ILocomotion *INextBot::GetLocomotionInterface( void ) const
{
// these base interfaces are lazy-allocated (instead of being fully instanced classes) for two reasons:
// 1) so the memory is only used if needed
// 2) so the component is registered properly
if ( m_baseLocomotion == NULL )
{
m_baseLocomotion = new ILocomotion( const_cast< INextBot * >( this ) );
}
return m_baseLocomotion;
}
inline IBody *INextBot::GetBodyInterface( void ) const
{
if ( m_baseBody == NULL )
{
m_baseBody = new IBody( const_cast< INextBot * >( this ) );
}
return m_baseBody;
}
inline IIntention *INextBot::GetIntentionInterface( void ) const
{
if ( m_baseIntention == NULL )
{
m_baseIntention = new IIntention( const_cast< INextBot * >( this ) );
}
return m_baseIntention;
}
inline IVision *INextBot::GetVisionInterface( void ) const
{
if ( m_baseVision == NULL )
{
m_baseVision = new IVision( const_cast< INextBot * >( this ) );
}
return m_baseVision;
}
inline int INextBot::GetBotId() const
{
return m_id;
}
inline void INextBot::FlagForUpdate( bool b )
{
m_bFlaggedForUpdate = b;
}
inline bool INextBot::IsFlaggedForUpdate()
{
return m_bFlaggedForUpdate;
}
inline int INextBot::GetTickLastUpdate() const
{
return m_tickLastUpdate;
}
inline void INextBot::SetTickLastUpdate( int tick )
{
m_tickLastUpdate = tick;
}
inline bool INextBot::IsImmobile( void ) const
{
return m_immobileTimer.HasStarted();
}
inline float INextBot::GetImmobileDuration( void ) const
{
return m_immobileTimer.GetElapsedTime();
}
inline void INextBot::ClearImmobileStatus( void )
{
m_immobileTimer.Invalidate();
m_immobileAnchor = GetEntity()->GetAbsOrigin();
}
inline float INextBot::GetImmobileSpeedThreshold( void ) const
{
return 30.0f;
}
inline INextBotEventResponder *INextBot::FirstContainedResponder( void ) const
{
return m_componentList;
}
inline INextBotEventResponder *INextBot::NextContainedResponder( INextBotEventResponder *current ) const
{
return static_cast< INextBotComponent * >( current )->m_nextComponent;
}
#endif // _NEXT_BOT_INTERFACE_H_
+155
View File
@@ -0,0 +1,155 @@
// NextBotKnownEntity.h
// Encapsulation of being aware of an entity
// Author: Michael Booth, June 2009
#ifndef NEXT_BOT_KNOWN_ENTITY_H
#define NEXT_BOT_KNOWN_ENTITY_H
//----------------------------------------------------------------------------
/**
* A "known entity" is an entity that we have seen or heard at some point
* and which may or may not be immediately visible to us right now but which
* we remember the last place we encountered it, and when.
*
* TODO: Enhance interface to allow for sets of areas where an unseen entity
* could potentially be, knowing his last position and his rate of movement.
*/
class CKnownEntity
{
public:
// constructing assumes we currently know about this entity
CKnownEntity( CBaseEntity *who )
{
m_who = who;
m_whenLastSeen = -1.0f;
m_whenLastBecameVisible = -1.0f;
m_isVisible = false;
m_whenBecameKnown = gpGlobals->curtime;
m_hasLastKnownPositionBeenSeen = false;
UpdatePosition();
}
virtual ~CKnownEntity() { }
virtual void Destroy( void )
{
m_who = NULL;
m_isVisible = false;
}
virtual void UpdatePosition( void ) // could be seen or heard, but now the entity's position is known
{
if ( m_who.Get() )
{
m_lastKnownPostion = m_who->GetAbsOrigin();
m_lastKnownArea = m_who->MyCombatCharacterPointer() ? m_who->MyCombatCharacterPointer()->GetLastKnownArea() : NULL;
m_whenLastKnown = gpGlobals->curtime;
}
}
virtual CBaseEntity *GetEntity( void ) const
{
return m_who;
}
virtual const Vector &GetLastKnownPosition( void ) const
{
return m_lastKnownPostion;
}
// Have we had a clear view of the last known position of this entity?
// This encapsulates the idea of "I just saw a guy right over *there* a few seconds ago, but I don't know where he is now"
virtual bool HasLastKnownPositionBeenSeen( void ) const
{
return m_hasLastKnownPositionBeenSeen;
}
virtual void MarkLastKnownPositionAsSeen( void )
{
m_hasLastKnownPositionBeenSeen = true;
}
virtual const CNavArea *GetLastKnownArea( void ) const
{
return m_lastKnownArea;
}
virtual float GetTimeSinceLastKnown( void ) const
{
return gpGlobals->curtime - m_whenLastKnown;
}
virtual float GetTimeSinceBecameKnown( void ) const
{
return gpGlobals->curtime - m_whenBecameKnown;
}
virtual void UpdateVisibilityStatus( bool visible )
{
if ( visible )
{
if ( !m_isVisible )
{
// just became visible
m_whenLastBecameVisible = gpGlobals->curtime;
}
m_whenLastSeen = gpGlobals->curtime;
}
m_isVisible = visible;
}
virtual bool IsVisible( void ) const // return true if this entity is currently visible
{
return m_isVisible;
}
virtual float GetTimeSinceBecameVisible( void ) const
{
return gpGlobals->curtime - m_whenLastBecameVisible;
}
virtual float GetTimeWhenBecameVisible( void ) const
{
return m_whenLastBecameVisible;
}
virtual float GetTimeSinceLastSeen( void ) const
{
return gpGlobals->curtime - m_whenLastSeen;
}
virtual bool WasEverVisible( void ) const
{
return m_whenLastSeen > 0.0f;
}
// has our knowledge of this entity become obsolete?
virtual bool IsObsolete( void ) const
{
return GetEntity() == NULL || !m_who->IsAlive() || GetTimeSinceLastKnown() > 10.0f;
}
virtual bool operator==( const CKnownEntity &other ) const
{
if ( GetEntity() == NULL || other.GetEntity() == NULL )
return false;
return ( GetEntity() == other.GetEntity() );
}
private:
CHandle< CBaseEntity > m_who;
Vector m_lastKnownPostion;
bool m_hasLastKnownPositionBeenSeen;
CNavArea *m_lastKnownArea;
float m_whenLastSeen;
float m_whenLastBecameVisible;
float m_whenLastKnown; // last seen or heard, confirming its existance
float m_whenBecameKnown;
bool m_isVisible; // flagged by IVision update as visible or not
};
#endif // NEXT_BOT_KNOWN_ENTITY_H
@@ -0,0 +1,512 @@
// NextBotLocomotionInterface.cpp
// Common functionality for all NextBot locomotors
// Author: Michael Booth, April 2005
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "BasePropDoor.h"
#include "nav_area.h"
#include "NextBot.h"
#include "NextBotUtil.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotBodyInterface.h"
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// how far a bot must move to not be considered "stuck"
#define STUCK_RADIUS 100.0f
//----------------------------------------------------------------------------------------------------------
/**
* Reset to initial state
*/
ILocomotion::ILocomotion( INextBot *bot ) : INextBotComponent( bot )
{
Reset();
}
ILocomotion::~ILocomotion()
{
}
void ILocomotion::Reset( void )
{
INextBotComponent::Reset();
m_motionVector = Vector( 1.0f, 0.0f, 0.0f );
m_speed = 0.0f;
m_groundMotionVector = m_motionVector;
m_groundSpeed = m_speed;
m_moveRequestTimer.Invalidate();
m_isStuck = false;
m_stuckTimer.Invalidate();
m_stuckPos = vec3_origin;
}
//----------------------------------------------------------------------------------------------------------
/**
* Update internal state
*/
void ILocomotion::Update( void )
{
StuckMonitor();
// maintain motion vector and speed values
const Vector &vel = GetVelocity();
m_speed = vel.Length();
m_groundSpeed = vel.AsVector2D().Length();
const float velocityThreshold = 10.0f;
if ( m_speed > velocityThreshold )
{
m_motionVector = vel / m_speed;
}
if ( m_groundSpeed > velocityThreshold )
{
m_groundMotionVector.x = vel.x / m_groundSpeed;
m_groundMotionVector.y = vel.y / m_groundSpeed;
m_groundMotionVector.z = 0.0f;
}
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
// show motion vector
NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_groundMotionVector, 3.0f, 100, 255, 0, 255, true, 0.1f );
NDebugOverlay::HorzArrow( GetFeet(), GetFeet() + 25.0f * m_motionVector, 5.0f, 255, 255, 0, 255, true, 0.1f );
}
}
//----------------------------------------------------------------------------
void ILocomotion::AdjustPosture( const Vector &moveGoal )
{
// This function has no effect if we're not standing or crouching
IBody *body = GetBot()->GetBodyInterface();
if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
return;
//
// Stand or crouch as needed
//
// get bounding limits, ignoring step-upable height
const Vector &mins = body->GetHullMins() + Vector( 0, 0, GetStepHeight() );
const float halfSize = body->GetHullWidth()/2.0f;
Vector standMaxs( halfSize, halfSize, body->GetStandHullHeight() );
trace_t trace;
NextBotTraversableTraceFilter filter( GetBot(), ILocomotion::IMMEDIATELY );
// snap forward movement vector along floor
const Vector &groundNormal = GetGroundNormal();
const Vector &feet = GetFeet();
Vector moveDir = moveGoal - feet;
float moveLength = moveDir.NormalizeInPlace();
Vector left( -moveDir.y, moveDir.x, 0.0f );
Vector goal = feet + moveLength * CrossProduct( left, groundNormal ).Normalized();
TraceHull( feet, goal, mins, standMaxs, body->GetSolidMask(), &filter, &trace );
if ( trace.fraction >= 1.0f && !trace.startsolid )
{
// no collision while standing
if ( body->IsActualPosture( IBody::CROUCH ) )
{
body->SetDesiredPosture( IBody::STAND );
}
return;
}
if ( body->IsActualPosture( IBody::CROUCH ) )
return;
// crouch hull check
Vector crouchMaxs( halfSize, halfSize, body->GetCrouchHullHeight() );
TraceHull( feet, goal, mins, crouchMaxs, body->GetSolidMask(), &filter, &trace );
if ( trace.fraction >= 1.0f && !trace.startsolid )
{
// no collision while crouching
body->SetDesiredPosture( IBody::CROUCH );
}
}
//----------------------------------------------------------------------------------------------------------
/**
* Move directly towards the given position
*/
void ILocomotion::Approach( const Vector &goalPos, float goalWeight )
{
// there is a desire to move
m_moveRequestTimer.Start();
}
//----------------------------------------------------------------------------------------------------------
/**
* Move the bot to the precise given position immediately
*/
void ILocomotion::DriveTo( const Vector &pos )
{
// there is a desire to move
m_moveRequestTimer.Start();
}
//----------------------------------------------------------------------------------------------------------
/**
* Return true if this locomotor could potentially move along the line given.
* If false is returned, fraction of walkable ray is returned in 'fraction'
*/
bool ILocomotion::IsPotentiallyTraversable( const Vector &from, const Vector &to, TraverseWhenType when, float *fraction ) const
{
VPROF_BUDGET( "Locomotion::IsPotentiallyTraversable", "NextBotExpensive" );
// if 'to' is high above us, it's not directly traversable
// Adding a bit of fudge room to allow for floating point roundoff errors
if ( ( to.z - from.z ) > GetMaxJumpHeight() + 0.1f )
{
Vector along = to - from;
along.NormalizeInPlace();
if ( along.z > GetTraversableSlopeLimit() )
{
if ( fraction )
{
*fraction = 0.0f;
}
return false;
}
}
trace_t result;
NextBotTraversableTraceFilter filter( GetBot(), when );
// use a small hull since we cannot simulate collision resolution and avoidance along the way
const float probeSize = 0.25f * GetBot()->GetBodyInterface()->GetHullWidth(); // 1.0f; Cant be TOO small, or open stairwells/grates/etc will cause problems
const float probeZ = GetStepHeight();
Vector hullMin( -probeSize, -probeSize, probeZ );
Vector hullMax( probeSize, probeSize, GetBot()->GetBodyInterface()->GetCrouchHullHeight() );
TraceHull( from, to, hullMin, hullMax, GetBot()->GetBodyInterface()->GetSolidMask(), &filter, &result );
/*
if ( result.DidHit() )
{
NDebugOverlay::SweptBox( from, result.endpos, hullMin, hullMax, vec3_angle, 255, 0, 0, 255, 9999.9f );
NDebugOverlay::SweptBox( result.endpos, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 9999.9f );
}
else
{
NDebugOverlay::SweptBox( from, to, hullMin, hullMax, vec3_angle, 255, 255, 0, 255, 0.1f );
}
*/
if ( fraction )
{
*fraction = result.fraction;
}
return ( result.fraction >= 1.0f ) && ( !result.startsolid );
}
//----------------------------------------------------------------------------------------------------------
/**
* Return true if there is a possible "gap" that will need to be jumped over
* If true is returned, fraction of ray before gap is returned in 'fraction'
*/
bool ILocomotion::HasPotentialGap( const Vector &from, const Vector &desiredTo, float *fraction ) const
{
VPROF_BUDGET( "Locomotion::HasPotentialGap", "NextBot" );
// find section of this ray that is actually traversable
float traversableFraction;
IsPotentiallyTraversable( from, desiredTo, IMMEDIATELY, &traversableFraction );
// compute end of traversable ray
Vector to = from + ( desiredTo - from ) * traversableFraction;
Vector forward = to - from;
float length = forward.NormalizeInPlace();
IBody *body = GetBot()->GetBodyInterface();
float step = body->GetHullWidth()/2.0f;
// scan along the line checking for gaps
Vector pos = from;
Vector delta = step * forward;
for( float t = 0.0f; t < (length + step); t += step )
{
if ( IsGap( pos, forward ) )
{
if ( fraction )
{
*fraction = ( t - step ) / ( length + step );
}
return true;
}
pos += delta;
}
if ( fraction )
{
*fraction = 1.0f;
}
return false;
}
//----------------------------------------------------------------------------------------------------------
/**
* Return true if there is a "gap" here when moving in the given direction.
* A "gap" is a vertical dropoff that is too high to jump back up to.
*/
bool ILocomotion::IsGap( const Vector &pos, const Vector &forward ) const
{
VPROF_BUDGET( "Locomotion::IsGap", "NextBotSpiky" );
IBody *body = GetBot()->GetBodyInterface();
//float halfWidth = ( body ) ? body->GetHullWidth()/2.0f : 1.0f;
// can't really jump effectively when crouched anyhow
//float hullHeight = ( body ) ? body->GetStandHullHeight() : 1.0f;
// use a small hull since we cannot simulate collision resolution and avoidance along the way
const float halfWidth = 1.0f;
const float hullHeight = 1.0f;
unsigned int mask = ( body ) ? body->GetSolidMask() : MASK_PLAYERSOLID;
trace_t ground;
NextBotTraceFilterIgnoreActors filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
TraceHull( pos + Vector( 0, 0, GetStepHeight() ), // start up a bit to handle rough terrain
pos + Vector( 0, 0, -GetMaxJumpHeight() ),
Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
mask, &filter, &ground );
// int r,g,b;
//
// if ( ground.fraction >= 1.0f && !ground.startsolid )
// {
// r = 255, g = 0, b = 0;
// }
// else
// {
// r = 0, g = 255, b = 0;
// }
//
// NDebugOverlay::SweptBox( pos,
// pos + Vector( 0, 0, -GetStepHeight() ),
// Vector( -halfWidth, -halfWidth, 0 ), Vector( halfWidth, halfWidth, hullHeight ),
// vec3_angle,
// r, g, b, 255, 3.0f );
// if trace hit nothing, there's a gap ahead of us
return ( ground.fraction >= 1.0f && !ground.startsolid );
}
//----------------------------------------------------------------------------------------------------------
bool ILocomotion::IsEntityTraversable( CBaseEntity *obstacle, TraverseWhenType when ) const
{
if ( obstacle->IsWorld() )
return false;
// assume bot will open a door in its path
if ( FClassnameIs( obstacle, "prop_door*" ) || FClassnameIs( obstacle, "func_door*" ) )
{
CBasePropDoor *door = dynamic_cast< CBasePropDoor * >( obstacle );
if ( door && door->IsDoorOpen() )
{
// open doors are obstacles
return false;
}
return true;
}
// if we hit a clip brush, ignore it if it is not BRUSHSOLID_ALWAYS
if ( FClassnameIs( obstacle, "func_brush" ) )
{
CFuncBrush *brush = (CFuncBrush *)obstacle;
switch ( brush->m_iSolidity )
{
case CFuncBrush::BRUSHSOLID_ALWAYS:
return false;
case CFuncBrush::BRUSHSOLID_NEVER:
return true;
case CFuncBrush::BRUSHSOLID_TOGGLE:
return true;
}
}
if ( when == IMMEDIATELY )
{
// special rules in specific games can immediately break some breakables, etc.
return false;
}
// assume bot will EVENTUALLY break breakables in its path
return GetBot()->IsAbleToBreak( obstacle );
}
//--------------------------------------------------------------------------------------------------------------
bool ILocomotion::IsAreaTraversable( const CNavArea *baseArea ) const
{
return !baseArea->IsBlocked( GetBot()->GetEntity()->GetTeamNumber() );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Reset stuck status to un-stuck
*/
void ILocomotion::ClearStuckStatus( const char *reason )
{
if ( IsStuck() )
{
m_isStuck = false;
// tell other components we're no longer stuck
GetBot()->OnUnStuck();
}
// always reset stuck monitoring data in case we cleared preemptively are were not yet stuck
m_stuckPos = GetFeet();
m_stuckTimer.Start();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
DevMsg( "%3.2f: ClearStuckStatus: %s %s\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), reason );
}
}
//--------------------------------------------------------------------------------------------------------------
/**
* Stuck check
*/
void ILocomotion::StuckMonitor( void )
{
// a timer is needed to smooth over a few frames of inactivity due to state changes, etc.
// we only want to detect idle situations when the bot really doesn't "want" to move.
const float idleTime = 0.25f;
if ( m_moveRequestTimer.IsGreaterThen( idleTime ) )
{
// we have no desire to move, and therefore cannot emit stuck events
// prepare our internal state for when the bot starts to move next
m_stuckPos = GetFeet();
m_stuckTimer.Start();
return;
}
// if ( !IsOnGround() )
// {
// // can't be stuck when in-air
// ClearStuckStatus( "Off the ground" );
// return;
// }
// if ( IsUsingLadder() )
// {
// // can't be stuck when on a ladder (for now)
// ClearStuckStatus( "On a ladder" );
// return;
// }
if ( IsStuck() )
{
// we are/were stuck - have we moved enough to consider ourselves "dislodged"
if ( GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
{
// we've just become un-stuck
ClearStuckStatus( "UN-STUCK" );
}
else
{
// still stuck - periodically resend the event
if ( m_stillStuckTimer.IsElapsed() )
{
m_stillStuckTimer.Start( 1.0f );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
DevMsg( "%3.2f: %s STILL STUCK\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier() );
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 1.0f );
}
GetBot()->OnStuck();
}
}
}
else
{
// we're not stuck - yet
if ( /*IsClimbingOrJumping() || */GetBot()->IsRangeGreaterThan( m_stuckPos, STUCK_RADIUS ) )
{
// we have moved - reset anchor
m_stuckPos = GetFeet();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::Cross3D( m_stuckPos, 3.0f, 255, 0, 255, true, 3.0f );
}
m_stuckTimer.Start();
}
else
{
// within stuck range of anchor. if we've been here too long, we're stuck
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::Line( GetBot()->GetEntity()->WorldSpaceCenter(), m_stuckPos, 255, 0, 255, true, 0.1f );
}
float minMoveSpeed = 0.1f * GetDesiredSpeed() + 0.1f;
float escapeTime = STUCK_RADIUS / minMoveSpeed;
if ( m_stuckTimer.IsGreaterThen( escapeTime ) )
{
// we have taken too long - we're stuck
m_isStuck = true;
if ( GetBot()->IsDebugging( NEXTBOT_ERRORS ) )
{
DevMsg( "%3.2f: %s STUCK at position( %3.2f, %3.2f, %3.2f )\n", gpGlobals->curtime, GetBot()->GetDebugIdentifier(), m_stuckPos.x, m_stuckPos.y, m_stuckPos.z );
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 15.0f ), QAngle( -90.0f, 0, 0 ), 3.0f, 255, 255, 0, 255, true, 1.0f );
NDebugOverlay::Circle( m_stuckPos + Vector( 0, 0, 5.0f ), QAngle( -90.0f, 0, 0 ), 5.0f, 255, 0, 0, 255, true, 9999999.9f );
}
// tell other components we've become stuck
GetBot()->OnStuck();
}
}
}
}
@@ -0,0 +1,334 @@
// NextBotLocomotionInterface.h
// NextBot interface for movement through the environment
// Author: Michael Booth, April 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_LOCOMOTION_INTERFACE_H_
#define _NEXT_BOT_LOCOMOTION_INTERFACE_H_
#include "NextBotComponentInterface.h"
class Path;
class INextBot;
class CNavLadder;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface encapsulating *how* a bot moves through the world (walking? flying? etc)
*/
class ILocomotion : public INextBotComponent
{
public:
ILocomotion( INextBot *bot );
virtual ~ILocomotion();
virtual void Reset( void ); // (EXTEND) reset to initial state
virtual void Update( void ); // (EXTEND) update internal state
//
// The primary locomotive method
// Depending on the physics of the bot's motion, it may not actually
// reach the given position precisely.
// The 'weight' can be used to combine multiple Approach() calls within
// a single frame into a single goal (ie: weighted average)
//
virtual void Approach( const Vector &goalPos, float goalWeight = 1.0f ); // (EXTEND) move directly towards the given position
//
// Move the bot to the precise given position immediately,
// updating internal state as needed
// Collision resolution is done to prevent interpenetration, which may prevent
// the bot from reaching the given position. If no collisions occur, the
// bot will be at the given position when this method returns.
//
virtual void DriveTo( const Vector &pos ); // (EXTEND) Move the bot to the precise given position immediately,
//
// Locomotion modifiers
//
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ) { return true; } // initiate a jump to an adjacent high ledge, return false if climb can't start
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ) { } // initiate a jump across an empty volume of space to far side
virtual void Jump( void ) { } // initiate a simple undirected jump in the air
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
virtual bool IsScrambling( void ) const; // is in the middle of a complex action (climbing a ladder, climbing a ledge, jumping, etc) that shouldn't be interrupted
virtual void Run( void ) { } // set desired movement speed to running
virtual void Walk( void ) { } // set desired movement speed to walking
virtual void Stop( void ) { } // set desired movement speed to stopped
virtual bool IsRunning( void ) const;
virtual void SetDesiredSpeed( float speed ) { } // set desired speed for locomotor movement
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
virtual void SetSpeedLimit( float speed ) { } // set maximum speed bot can reach, regardless of desired speed
virtual float GetSpeedLimit( void ) const { return 1000.0f; } // get maximum speed bot can reach, regardless of desired speed
virtual bool IsOnGround( void ) const; // return true if standing on something
virtual void OnLeaveGround( CBaseEntity *ground ) { } // invoked when bot leaves ground for any reason
virtual void OnLandOnGround( CBaseEntity *ground ) { } // invoked when bot lands on the ground after being in the air
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
virtual float GetGroundSpeed( void ) const; // return current world space speed in XY plane
virtual const Vector &GetGroundMotionVector( void ) const; // return unit vector in XY plane describing our direction of motion - even if we are currently not moving
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ) { } // climb the given ladder to the top and dismount
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ) { } // descend the given ladder to the bottom and dismount
virtual bool IsUsingLadder( void ) const; // we are moving to get on, ascending/descending, and/or dismounting a ladder
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
virtual bool IsAbleToAutoCenterOnLadder( void ) const { return false; }
virtual void FaceTowards( const Vector &target ) { } // rotate body to face towards "target"
virtual void SetDesiredLean( const QAngle &lean ) { }
virtual const QAngle &GetDesiredLean( void ) const;
//
// Locomotion information
//
virtual bool IsAbleToJumpAcrossGaps( void ) const; // return true if this bot can jump across gaps in its path
virtual bool IsAbleToClimb( void ) const; // return true if this bot can climb arbitrary geometry it encounters
virtual const Vector &GetFeet( void ) const; // return position of "feet" - the driving point where the bot contacts the ground
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
virtual float GetSpeed( void ) const; // return current world space speed (magnitude of velocity)
virtual const Vector &GetMotionVector( void ) const; // return unit vector describing our direction of motion - even if we are currently not moving
virtual bool IsAreaTraversable( const CNavArea *baseArea ) const; // return true if given area can be used for navigation
virtual float GetTraversableSlopeLimit( void ) const; // return Z component of unit normal of steepest traversable slope
// return true if the given entity can be ignored during locomotion
enum TraverseWhenType
{
IMMEDIATELY, // the entity will not block our motion - we'll carry right through
EVENTUALLY // the entity will block us until we spend effort to open/destroy it
};
/**
* Return true if this locomotor could potentially move along the line given.
* If false is returned, fraction of walkable ray is returned in 'fraction'
*/
virtual bool IsPotentiallyTraversable( const Vector &from, const Vector &to, TraverseWhenType when = EVENTUALLY, float *fraction = NULL ) const;
/**
* Return true if there is a possible "gap" that will need to be jumped over
* If true is returned, fraction of ray before gap is returned in 'fraction'
*/
virtual bool HasPotentialGap( const Vector &from, const Vector &to, float *fraction = NULL ) const;
// return true if there is a "gap" here when moving in the given direction
virtual bool IsGap( const Vector &pos, const Vector &forward ) const;
virtual bool IsEntityTraversable( CBaseEntity *obstacle, TraverseWhenType when = EVENTUALLY ) const;
//
// Stuck state. If the locomotor cannot make progress, it becomes "stuck" and can only leave
// this stuck state by successfully moving and becoming un-stuck.
//
virtual bool IsStuck( void ) const; // return true if bot is stuck
virtual float GetStuckDuration( void ) const; // return how long we've been stuck
virtual void ClearStuckStatus( const char *reason = "" ); // reset stuck status to un-stuck
virtual bool IsAttemptingToMove( void ) const; // return true if we have tried to Approach() or DriveTo() very recently
void TraceHull( const Vector& start, const Vector& end, const Vector &mins, const Vector &maxs, unsigned int fMask, ITraceFilter *pFilter, trace_t *pTrace ) const;
protected:
virtual void AdjustPosture( const Vector &moveGoal );
virtual void StuckMonitor( void );
private:
Vector m_motionVector;
Vector m_groundMotionVector;
float m_speed;
float m_groundSpeed;
// stuck monitoring
bool m_isStuck; // if true, we are stuck
IntervalTimer m_stuckTimer; // how long we've been stuck
CountdownTimer m_stillStuckTimer; // for resending stuck events
Vector m_stuckPos; // where we got stuck
IntervalTimer m_moveRequestTimer;
};
inline bool ILocomotion::IsAbleToJumpAcrossGaps( void ) const
{
return true;
}
inline bool ILocomotion::IsAbleToClimb( void ) const
{
return true;
}
inline bool ILocomotion::IsAttemptingToMove( void ) const
{
return m_moveRequestTimer.HasStarted() && m_moveRequestTimer.GetElapsedTime() < 0.25f;
}
inline bool ILocomotion::IsScrambling( void ) const
{
return !IsOnGround() || IsClimbingOrJumping() || IsAscendingOrDescendingLadder();
}
inline bool ILocomotion::IsClimbingOrJumping( void ) const
{
return false;
}
inline bool ILocomotion::IsClimbingUpToLedge( void ) const
{
return false;
}
inline bool ILocomotion::IsJumpingAcrossGap( void ) const
{
return false;
}
inline bool ILocomotion::IsRunning( void ) const
{
return false;
}
inline float ILocomotion::GetDesiredSpeed( void ) const
{
return 0.0f;
}
inline bool ILocomotion::IsOnGround( void ) const
{
return false;
}
inline CBaseEntity *ILocomotion::GetGround( void ) const
{
return NULL;
}
inline const Vector &ILocomotion::GetGroundNormal( void ) const
{
return vec3_origin;
}
inline float ILocomotion::GetGroundSpeed( void ) const
{
return m_groundSpeed;
}
inline const Vector & ILocomotion::GetGroundMotionVector( void ) const
{
return m_groundMotionVector;
}
inline bool ILocomotion::IsUsingLadder( void ) const
{
return false;
}
inline bool ILocomotion::IsAscendingOrDescendingLadder( void ) const
{
return false;
}
inline const QAngle &ILocomotion::GetDesiredLean( void ) const
{
return vec3_angle;
}
inline const Vector &ILocomotion::GetFeet( void ) const
{
return vec3_origin;
}
inline float ILocomotion::GetStepHeight( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetMaxJumpHeight( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetDeathDropHeight( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetRunSpeed( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetWalkSpeed( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetMaxAcceleration( void ) const
{
return 0.0f;
}
inline float ILocomotion::GetMaxDeceleration( void ) const
{
return 0.0f;
}
inline const Vector &ILocomotion::GetVelocity( void ) const
{
return vec3_origin;
}
inline float ILocomotion::GetSpeed( void ) const
{
return m_speed;
}
inline const Vector & ILocomotion::GetMotionVector( void ) const
{
return m_motionVector;
}
inline float ILocomotion::GetTraversableSlopeLimit( void ) const
{
return 0.6;
}
inline bool ILocomotion::IsStuck( void ) const
{
return m_isStuck;
}
inline float ILocomotion::GetStuckDuration( void ) const
{
return ( IsStuck() ) ? m_stuckTimer.GetElapsedTime() : 0.0f;
}
inline void ILocomotion::TraceHull( const Vector& start, const Vector& end, const Vector &mins, const Vector &maxs, unsigned int fMask, ITraceFilter *pFilter, trace_t *pTrace ) const
{
// VPROF_BUDGET( "ILocomotion::TraceHull", "TraceHull" );
Ray_t ray;
ray.Init( start, end, mins, maxs );
enginetrace->TraceRay( ray, fMask, pFilter, pTrace );
}
#endif // _NEXT_BOT_LOCOMOTION_INTERFACE_H_
+947
View File
@@ -0,0 +1,947 @@
// NextBotManager.cpp
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "NextBotManager.h"
#include "NextBotInterface.h"
#ifdef TERROR
#include "ZombieBot/Infected/Infected.h"
#include "ZombieBot/Witch/Witch.h"
#include "ZombieManager.h"
#endif
#include "SharedFunctorUtils.h"
//#include "../../common/blackbox_helper.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern ConVar ZombieMobMaxSize;
ConVar nb_update_frequency( "nb_update_frequency", ".1", FCVAR_CHEAT );
ConVar nb_update_framelimit( "nb_update_framelimit", ( IsDebug() ) ? "30" : "15", FCVAR_CHEAT );
ConVar nb_update_maxslide( "nb_update_maxslide", "2", FCVAR_CHEAT );
ConVar nb_update_debug( "nb_update_debug", "0", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
/**
* Singleton accessor.
* By returning a reference, we guarantee construction of the
* instance before its first use.
*/
NextBotManager &TheNextBots( void )
{
static NextBotManager manager;
return manager;
}
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
static const char *debugTypeName[] =
{
"BEHAVIOR",
"LOOK_AT",
"PATH",
"ANIMATION",
"LOCOMOTION",
"VISION",
"HEARING",
"EVENTS",
"ERRORS",
NULL
};
static void CC_SetDebug( const CCommand &args )
{
if ( args.ArgC() < 2 )
{
Msg( "Debugging stopped\n" );
TheNextBots().SetDebugTypes( NEXTBOT_DEBUG_NONE );
return;
}
int debugType = 0;
for( int i=1; i<args.ArgC(); ++i )
{
int type;
for( type = 0; debugTypeName[ type ]; ++type )
{
const char *token = args[i];
// special token that means "all"
if ( token[0] == '*' )
{
debugType = NEXTBOT_DEBUG_ALL;
break;
}
if ( !Q_strnicmp( args[i], debugTypeName[ type ], Q_strlen( args[1] ) ) )
{
debugType |= ( 1 << type );
break;
}
}
if ( !debugTypeName[ type ] )
{
Msg( "Invalid debug type '%s'\n", args[i] );
}
}
// enable debugging
TheNextBots().SetDebugTypes( ( NextBotDebugType ) debugType );
}
static ConCommand SetDebug( "nb_debug", CC_SetDebug, "Debug NextBots. Categories are: BEHAVIOR, LOOK_AT, PATH, ANIMATION, LOCOMOTION, VISION, HEARING, EVENTS, ERRORS.", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
static void CC_SetDebugFilter( const CCommand &args )
{
if ( args.ArgC() < 2 )
{
Msg( "Debug filter cleared.\n" );
TheNextBots().DebugFilterClear();
return;
}
for( int i=1; i<args.ArgC(); ++i )
{
int index = Q_atoi( args[i] );
if ( index > 0 )
{
TheNextBots().DebugFilterAdd( index );
}
else
{
TheNextBots().DebugFilterAdd( args[i] );
}
}
}
static ConCommand SetDebugFilter( "nb_debug_filter", CC_SetDebugFilter, "Add items to the NextBot debug filter. Items can be entindexes or part of the indentifier of one or more bots.", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
class Selector
{
public:
Selector( CBasePlayer *player, bool useLOS )
{
m_player = player;
player->EyeVectors( &m_forward );
m_pick = NULL;
m_pickRange = 99999999999999.9f;
m_useLOS = useLOS;
}
bool operator() ( INextBot *bot )
{
CBaseCombatCharacter *botEntity = bot->GetEntity();
if ( botEntity->IsAlive() )
{
Vector to = botEntity->WorldSpaceCenter() - m_player->EyePosition();
float range = to.NormalizeInPlace();
if ( DotProduct( m_forward, to ) > 0.98f && range < m_pickRange )
{
if ( !m_useLOS || m_player->IsAbleToSee( botEntity, CBaseCombatCharacter::DISREGARD_FOV ) )
{
m_pick = bot;
m_pickRange = range;
}
}
}
return true;
}
CBasePlayer *m_player;
Vector m_forward;
INextBot *m_pick;
float m_pickRange;
bool m_useLOS;
};
static void CC_SelectBot( const CCommand &args )
{
CBasePlayer *player = UTIL_GetListenServerHost();
if ( player )
{
Selector select( player, false );
TheNextBots().ForEachBot( select );
TheNextBots().Select( select.m_pick );
if ( select.m_pick )
{
NDebugOverlay::Circle( select.m_pick->GetLocomotionInterface()->GetFeet() + Vector( 0, 0, 5 ), Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 25.0f, 0, 255, 0, 255, false, 1.0f );
}
}
}
static ConCommand SelectBot( "nb_select", CC_SelectBot, "Select the bot you are aiming at for further debug operations.", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
static void CC_ForceLookAt( const CCommand &args )
{
CBasePlayer *player = UTIL_GetListenServerHost();
INextBot *pick = TheNextBots().GetSelected();
if ( player && pick )
{
pick->GetBodyInterface()->AimHeadTowards( player, IBody::CRITICAL, 9999999.9f, NULL, "Aim forced" );
}
}
static ConCommand ForceLookAt( "nb_force_look_at", CC_ForceLookAt, "Force selected bot to look at the local player's position", FCVAR_CHEAT );
//--------------------------------------------------------------------------------------------------------
void CC_WarpSelectedHere( const CCommand &args )
{
CBasePlayer *me = dynamic_cast< CBasePlayer * >( UTIL_GetCommandClient() );
INextBot *pick = TheNextBots().GetSelected();
if ( me == NULL || pick == NULL )
{
return;
}
Vector forward;
me->EyeVectors( &forward );
trace_t result;
UTIL_TraceLine( me->EyePosition(), me->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, me, COLLISION_GROUP_NONE, &result );
if ( result.DidHit() )
{
Vector spot = result.endpos + Vector( 0, 0, 10.0f );
pick->GetEntity()->Teleport( &spot, &vec3_angle, &vec3_origin );
}
}
static ConCommand WarpSelectedHere( "nb_warp_selected_here", CC_WarpSelectedHere, "Teleport the selected bot to your cursor position", FCVAR_CHEAT );
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
NextBotManager::NextBotManager( void )
{
m_debugType = 0;
m_selectedBot = NULL;
m_iUpdateTickrate = 0;
}
//---------------------------------------------------------------------------------------------
NextBotManager::~NextBotManager()
{
}
//---------------------------------------------------------------------------------------------
/**
* Reset to initial state
*/
void NextBotManager::Reset( void )
{
// remove the NextBots that should go away during a reset (they will unregister themselves as they go)
int i = m_botList.Head();
while ( i != m_botList.InvalidIndex() )
{
int iNext = m_botList.Next( i );
if ( m_botList[i]->IsRemovedOnReset() )
{
UTIL_Remove( m_botList[i]->GetEntity() );
//Assert( !m_botList.IsInList( i ) ); // UTIL_Remove() calls UpdateOnRemove, adds EFL_KILLME, but doesn't delete until the end of the frame
}
i = iNext;
}
m_selectedBot = NULL;
}
//---------------------------------------------------------------------------------------------
inline bool IsDead( INextBot *pBot )
{
CBaseCombatCharacter *pEntity = pBot->GetEntity();
if ( pEntity )
{
if ( pEntity->IsPlayer() && pEntity->m_lifeState == LIFE_DEAD )
{
return true;
}
if ( pEntity->IsMarkedForDeletion() )
{
return true;
}
if ( pEntity->m_pfnThink == &CBaseEntity::SUB_Remove )
{
return true;
}
}
return false;
}
//---------------------------------------------------------------------------------------------
// Debug stats for update balancing
static int g_nRun;
static int g_nSlid;
static int g_nBlockedSlides;
void NextBotManager::Update( void )
{
// do lightweight upkeep every tick
for( int u=m_botList.Head(); u != m_botList.InvalidIndex(); u = m_botList.Next( u ) )
{
m_botList[ u ]->Upkeep();
}
// schedule full updates
if ( m_botList.Count() )
{
static int iCurFrame = -1;
if ( iCurFrame != gpGlobals->framecount )
{
iCurFrame = gpGlobals->framecount;
m_SumFrameTime = 0;
}
else
{
// Don't run multiple ticks in a frame
return;
}
int tickRate = TIME_TO_TICKS( nb_update_frequency.GetFloat() );
if ( tickRate < 0 )
{
tickRate = 0;
}
if ( m_iUpdateTickrate != tickRate )
{
Msg( "NextBot tickrate changed from %d (%.3fms) to %d (%.3fms)\n", m_iUpdateTickrate, TICKS_TO_TIME( m_iUpdateTickrate ), tickRate, TICKS_TO_TIME( tickRate ) );
m_iUpdateTickrate = tickRate;
}
int i = 0;
int nScheduled = 0;
int nNonResponsive = 0;
int nDead = 0;
if ( m_iUpdateTickrate > 0 )
{
INextBot *pBot;
// Count dead bots, they won't update and balancing calculations should exclude them
for( i = m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
if ( IsDead( m_botList[i] ) )
{
nDead++;
}
}
int nTargetToRun = ceilf( (float)( m_botList.Count() - nDead ) / (float)m_iUpdateTickrate );
int curtickcount = gpGlobals->tickcount;
for( i = m_botList.Head(); nTargetToRun && i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
pBot = m_botList[i];
if ( pBot->IsFlaggedForUpdate() )
{
// Was offered a run last tick but didn't take it, push it back
// Leave the flag set so that bot will run right away later, but be ignored
// until then
nNonResponsive++;
}
else
{
if ( curtickcount - pBot->GetTickLastUpdate() < m_iUpdateTickrate )
{
break;
}
if ( !IsDead( pBot ) )
{
pBot->FlagForUpdate();
nTargetToRun--;
nScheduled++;
}
}
}
}
else
{
nScheduled = m_botList.Count();
}
if ( nb_update_debug.GetBool() )
{
int nIntentionalSliders = 0;
if ( m_iUpdateTickrate > 0 )
{
for( ; i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
if ( gpGlobals->tickcount - m_botList[i]->GetTickLastUpdate() >= m_iUpdateTickrate )
{
nIntentionalSliders++;
}
}
}
Msg( "Frame %8d/tick %8d: %3d run of %3d, %3d sliders, %3d blocked slides, scheduled %3d for next tick, %3d intentional sliders, %d nonresponsive, %d dead\n", gpGlobals->framecount - 1, gpGlobals->tickcount - 1, g_nRun, m_botList.Count() - nDead, g_nSlid, g_nBlockedSlides, nScheduled, nIntentionalSliders, nNonResponsive, nDead );
g_nRun = g_nSlid = g_nBlockedSlides = 0;
}
}
}
//---------------------------------------------------------------------------------------------
bool NextBotManager::ShouldUpdate( INextBot *bot )
{
if ( m_iUpdateTickrate < 1 )
{
return true;
}
float frameLimit = nb_update_framelimit.GetFloat();
float sumFrameTime = 0;
if ( bot->IsFlaggedForUpdate() )
{
bot->FlagForUpdate( false );
sumFrameTime = m_SumFrameTime * 1000.0;
if ( frameLimit > 0.0f )
{
if ( sumFrameTime < frameLimit )
{
return true;
}
else if ( nb_update_debug.GetBool() )
{
Msg( "Frame %8d/tick %8d: frame out of budget (%.2fms > %.2fms)\n", gpGlobals->framecount, gpGlobals->tickcount, sumFrameTime, frameLimit );
}
}
}
int nTicksSlid = ( gpGlobals->tickcount - bot->GetTickLastUpdate() ) - m_iUpdateTickrate;
if ( nTicksSlid >= nb_update_maxslide.GetInt() )
{
if ( frameLimit == 0.0 || sumFrameTime < nb_update_framelimit.GetFloat() * 2.0 )
{
g_nBlockedSlides++;
return true;
}
}
if ( nb_update_debug.GetBool() )
{
if ( nTicksSlid > 0 )
{
g_nSlid++;
}
}
return false;
}
//---------------------------------------------------------------------------------------------
void NextBotManager::NotifyBeginUpdate( INextBot *bot )
{
if ( nb_update_debug.GetBool() )
{
g_nRun++;
}
m_botList.Unlink( bot->GetBotId() );
m_botList.LinkToTail( bot->GetBotId() );
bot->SetTickLastUpdate( gpGlobals->tickcount );
m_CurUpdateStartTime = Plat_FloatTime();
}
//---------------------------------------------------------------------------------------------
void NextBotManager::NotifyEndUpdate( INextBot *bot )
{
// This might be a good place to detect a particular bot had spiked [3/14/2008 tom]
m_SumFrameTime += Plat_FloatTime() - m_CurUpdateStartTime;
}
//---------------------------------------------------------------------------------------------
/**
* When the server has changed maps
*/
void NextBotManager::OnMapLoaded( void )
{
Reset();
}
//---------------------------------------------------------------------------------------------
/**
* When the scenario restarts
*/
void NextBotManager::OnRoundRestart( void )
{
Reset();
}
//---------------------------------------------------------------------------------------------
int NextBotManager::Register( INextBot *bot )
{
return m_botList.AddToHead( bot );
}
//---------------------------------------------------------------------------------------------
void NextBotManager::UnRegister( INextBot *bot )
{
m_botList.Remove( bot->GetBotId() );
if ( bot == m_selectedBot)
{
// we can't access virtual methods because this is called from a destructor, so just clear it
m_selectedBot = NULL;
}
}
//---------------------------------------------------------------------------------------------
#ifdef TERROR
extern ConVar ZombieNoticeItRange;
class NextBotAttackTheIT
{
public:
NextBotAttackTheIT( CTerrorPlayer *victim, float range )
{
m_victim = victim;
m_chaseCount = 0;
m_range = range;
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->GetTeamNumber() == TEAM_ZOMBIE && bot->GetEntity()->IsAlive() )
{
if ( ( bot->GetPosition() - m_victim->GetAbsOrigin() ).IsLengthLessThan( m_range ) )
{
// IT does not aggro Witches
Witch *witch = dynamic_cast< Witch * >( bot );
if ( !witch )
{
// close enough to smell the blood - attack the "it"!
bot->OnCommandAttack( m_victim );
++m_chaseCount;
}
}
}
return true;
}
CTerrorPlayer *m_victim;
int m_chaseCount;
float m_range;
};
//---------------------------------------------------------------------------------------------
/**
* When a Survivor has been hit by Boomer Vomit
*/
void NextBotManager::OnSurvivorVomitedUpon( CTerrorPlayer *victim )
{
NextBotAttackTheIT attackIT( victim, ZombieNoticeItRange.GetFloat() );
ForEachBot( attackIT );
// make sure a mob chases the IT victim
int desiredCount = ZombieMobMaxSize.GetFloat();
UTIL_LogPrintf( "(MOB) %d wanderers grabbed for an IT mob of desired size %d.\n", attackIT.m_chaseCount, desiredCount );
if ( attackIT.m_chaseCount < desiredCount )
{
// fill out the IT mob a bit
TheZombieManager->SpawnITMob( desiredCount - attackIT.m_chaseCount );
}
// either via wanderers or an explicit mob, there was a rush
TheDirector->OnMobRushStart();
}
//---------------------------------------------------------------------------------------------
CON_COMMAND_F( nb_rush, "Causes all infected to rush the survivors.", FCVAR_CHEAT )
{
CTerrorPlayer *victim = TheDirector->GetRandomSurvivor();
if ( !victim )
return;
const float RushRange = 10000.0f; // everyone, not just bots in IT range.
NextBotAttackTheIT attackIT( victim, RushRange );
TheNextBots().ForEachBot( attackIT );
}
#endif // TERROR
//--------------------------------------------------------------------------------------------------------
void NextBotManager::OnBeginChangeLevel( void )
{
}
//----------------------------------------------------------------------------------------------------------
class NextBotKilledNotifyScan
{
public:
NextBotKilledNotifyScan( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
m_victim = victim;
m_info = info;
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_victim ) )
{
bot->OnOtherKilled( m_victim, m_info );
}
return true;
}
CBaseCombatCharacter *m_victim;
CTakeDamageInfo m_info;
};
//---------------------------------------------------------------------------------------------
/**
* When an actor is killed. Propagate to all NextBots.
*/
void NextBotManager::OnKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info )
{
NextBotKilledNotifyScan notify( victim, info );
TheNextBots().ForEachBot( notify );
}
//----------------------------------------------------------------------------------------------------------
class NextBotSoundNotifyScan
{
public:
NextBotSoundNotifyScan( CBaseEntity *source, const Vector &pos, KeyValues *keys ) : m_source( source ), m_pos( pos ), m_keys( keys )
{
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->IsAlive() && !bot->IsSelf( m_source ) )
{
bot->OnSound( m_source, m_pos, m_keys );
}
return true;
}
CBaseEntity *m_source;
const Vector &m_pos;
KeyValues *m_keys;
};
//---------------------------------------------------------------------------------------------
/**
* When an entity emits a sound
*/
void NextBotManager::OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys )
{
NextBotSoundNotifyScan notify( source, pos, keys );
TheNextBots().ForEachBot( notify );
if ( source && IsDebugging( NEXTBOT_HEARING ) )
{
int r,g,b;
switch( source->GetTeamNumber() )
{
case FIRST_GAME_TEAM: r = 0; g = 255; b = 0; break;
case (FIRST_GAME_TEAM+1): r = 255; g = 0; b = 0; break;
default: r = 255; g = 255; b = 0; break;
}
NDebugOverlay::Circle( pos, Vector( 1, 0, 0 ), Vector( 0, -1, 0 ), 5.0f, r, g, b, 255, true, 3.0f );
}
}
//----------------------------------------------------------------------------------------------------------
class NextBotResponseNotifyScan
{
public:
NextBotResponseNotifyScan( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ) : m_who( who ), m_concept( concept ), m_response( response )
{
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->IsAlive() )
{
bot->OnSpokeConcept( m_who, m_concept, m_response );
}
return true;
}
CBaseCombatCharacter *m_who;
AIConcept_t m_concept;
AI_Response *m_response;
};
//---------------------------------------------------------------------------------------------
/**
* When an Actor speaks a concept
*/
void NextBotManager::OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response )
{
NextBotResponseNotifyScan notify( who, concept, response );
TheNextBots().ForEachBot( notify );
if ( IsDebugging( NEXTBOT_HEARING ) )
{
// const char *who = response->GetCriteria()->GetValue( response->GetCriteria()->FindCriterionIndex( "Who" ) );
// TODO: Need concept.GetStringConcept()
DevMsg( "%3.2f: OnSpokeConcept( %s, %s )\n", gpGlobals->curtime, who->GetDebugName(), "concept.GetStringConcept()" );
}
}
//----------------------------------------------------------------------------------------------------------
class NextBotWeaponFiredNotifyScan
{
public:
NextBotWeaponFiredNotifyScan( CBaseCombatCharacter *who, CBaseCombatWeapon *weapon ) : m_who( who ), m_weapon( weapon )
{
}
bool operator() ( INextBot *bot )
{
if ( bot->GetEntity()->IsAlive() )
{
bot->OnWeaponFired( m_who, m_weapon );
}
return true;
}
CBaseCombatCharacter *m_who;
CBaseCombatWeapon *m_weapon;
};
//---------------------------------------------------------------------------------------------
/**
* When someone fires a weapon
*/
void NextBotManager::OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon )
{
NextBotWeaponFiredNotifyScan notify( whoFired, weapon );
TheNextBots().ForEachBot( notify );
if ( IsDebugging( NEXTBOT_EVENTS ) )
{
DevMsg( "%3.2f: OnWeaponFired( %s, %s )\n", gpGlobals->curtime, whoFired->GetDebugName(), weapon->GetName() );
}
}
//---------------------------------------------------------------------------------------------
/**
* Add given entindex to the debug filter
*/
void NextBotManager::DebugFilterAdd( int index )
{
DebugFilter filter;
filter.index = index;
filter.name[0] = '\000';
m_debugFilterList.AddToTail( filter );
}
//---------------------------------------------------------------------------------------------
/**
* Add given name to the debug filter
*/
void NextBotManager::DebugFilterAdd( const char *name )
{
DebugFilter filter;
filter.index = -1;
Q_strncpy( filter.name, name, DebugFilter::MAX_DEBUG_NAME_SIZE );
m_debugFilterList.AddToTail( filter );
}
//---------------------------------------------------------------------------------------------
/**
* Remove given entindex from the debug filter
*/
void NextBotManager::DebugFilterRemove( int index )
{
for( int i=0; i<m_debugFilterList.Count(); ++i )
{
if ( m_debugFilterList[i].index == index )
{
m_debugFilterList.Remove( i );
break;
}
}
}
//---------------------------------------------------------------------------------------------
/**
* Remove given name from the debug filter
*/
void NextBotManager::DebugFilterRemove( const char *name )
{
for( int i=0; i<m_debugFilterList.Count(); ++i )
{
if ( m_debugFilterList[i].name[0] != '\000' &&
!Q_strnicmp( name, m_debugFilterList[i].name, MIN( Q_strlen( name ), sizeof( m_debugFilterList[i].name ) ) ) )
{
m_debugFilterList.Remove( i );
break;
}
}
}
//---------------------------------------------------------------------------------------------
/**
* Clear the debug filter (remove all entries)
*/
void NextBotManager::DebugFilterClear( void )
{
m_debugFilterList.RemoveAll();
}
//---------------------------------------------------------------------------------------------
/**
* Return true if the given bot matches the debug filter
*/
bool NextBotManager::IsDebugFilterMatch( const INextBot *bot ) const
{
// if the filter is empty, all bots match
if ( m_debugFilterList.Count() == 0 )
{
return true;
}
for( int i=0; i<m_debugFilterList.Count(); ++i )
{
// compare entity index
if ( m_debugFilterList[i].index == const_cast< INextBot * >( bot )->GetEntity()->entindex() )
{
return true;
}
// compare debug filter
if ( m_debugFilterList[i].name[0] != '\000' && bot->IsDebugFilterMatch( m_debugFilterList[i].name ) )
{
return true;
}
// compare special keyword meaning local player is looking at them
if ( !Q_strnicmp( m_debugFilterList[i].name, "lookat", Q_strlen( m_debugFilterList[i].name ) ) )
{
CBasePlayer *watcher = UTIL_GetListenServerHost();
if ( watcher )
{
CBaseEntity *subject = watcher->GetObserverTarget();
if ( subject && bot->IsSelf( subject ) )
{
return true;
}
}
}
// compare special keyword meaning NextBot is selected
if ( !Q_strnicmp( m_debugFilterList[i].name, "selected", Q_strlen( m_debugFilterList[i].name ) ) )
{
INextBot *selected = GetSelected();
if ( selected && bot->IsSelf( selected->GetEntity() ) )
{
return true;
}
}
}
return false;
}
//---------------------------------------------------------------------------------------------
/**
* Get the bot under the given player's crosshair
*/
INextBot *NextBotManager::GetBotUnderCrosshair( CBasePlayer *picker )
{
if ( !picker )
return NULL;
const float MaxDot = 0.7f;
const float MaxRange = 4000.0f;
TargetScan< CBaseCombatCharacter > scan( picker, TEAM_ANY, 1.0f - MaxDot, MaxRange );
ForEachCombatCharacter( scan );
CBaseCombatCharacter *target = scan.GetTarget();
if ( target && target->MyNextBotPointer() )
return target->MyNextBotPointer();
return NULL;
}
#ifdef NEED_BLACK_BOX
//---------------------------------------------------------------------------------------------
CON_COMMAND( nb_dump_debug_history, "Dumps debug history for the bot under the cursor to the blackbox" )
{
if ( !NextBotDebugHistory.GetBool() )
{
BlackBox_Record( "bot", "nb_debug_history 0" );
return;
}
CBasePlayer *player = UTIL_GetCommandClient();
if ( !player )
{
player = UTIL_GetListenServerHost();
}
INextBot *bot = TheNextBots().GetBotUnderCrosshair( player );
if ( !bot )
{
BlackBox_Record( "bot", "no bot under crosshairs" );
return;
}
CUtlVector< const INextBot::NextBotDebugLineType * > lines;
bot->GetDebugHistory( (NEXTBOT_DEBUG_ALL & (~NEXTBOT_EVENTS)), &lines );
for ( int i=0; i<lines.Count(); ++i )
{
if ( IsPC() )
{
BlackBox_Record( "bot", "%s", lines[i]->data );
}
}
}
#endif // NEED_BLACK_BOX
+198
View File
@@ -0,0 +1,198 @@
// NextBotManager.h
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_MANAGER_H_
#define _NEXT_BOT_MANAGER_H_
#include "NextBotInterface.h"
class CTerrorPlayer;
//----------------------------------------------------------------------------------------------------------------
/**
* The NextBotManager manager
*/
class NextBotManager
{
public:
NextBotManager( void );
~NextBotManager();
void Reset( void ); // reset to initial state
void Update( void );
bool ShouldUpdate( INextBot *bot );
void NotifyBeginUpdate( INextBot *bot );
void NotifyEndUpdate( INextBot *bot );
int GetNextBotCount( void ) const; // How many nextbots are alive right now?
/**
* Execute functor for each NextBot in the system.
* If a functor returns false, stop iteration early
* and return false.
*/
template < typename Functor >
bool ForEachBot( Functor &func )
{
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
if ( !func( m_botList[i] ) )
{
return false;
}
}
return true;
}
/**
* Execute functor for each NextBot in the system as
* a CBaseCombatCharacter.
* If a functor returns false, stop iteration early
* and return false.
*/
template < typename Functor >
bool ForEachCombatCharacter( Functor &func )
{
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
if ( !func( m_botList[i]->GetEntity() ) )
{
return false;
}
}
return true;
}
/**
* Return closest bot to given point that passes the given filter
*/
template < typename Filter >
INextBot *GetClosestBot( const Vector &pos, Filter &filter )
{
INextBot *close = NULL;
float closeRangeSq = FLT_MAX;
for( int i=m_botList.Head(); i != m_botList.InvalidIndex(); i = m_botList.Next( i ) )
{
float rangeSq = ( m_botList[i]->GetEntity()->GetAbsOrigin() - pos ).LengthSqr();
if ( rangeSq < closeRangeSq && filter( m_botList[i] ) )
{
closeRangeSq = rangeSq;
close = m_botList[i];
}
}
return close;
}
/**
* Event propagators
*/
void OnMapLoaded( void ); // when the server has changed maps
void OnRoundRestart( void ); // when the scenario restarts
void OnBeginChangeLevel( void ); // when the server is about to change maps
virtual void OnKilled( CBaseCombatCharacter *victim, const CTakeDamageInfo &info ); // when an actor is killed
virtual void OnSound( CBaseEntity *source, const Vector &pos, KeyValues *keys ); // when an entity emits a sound
virtual void OnSpokeConcept( CBaseCombatCharacter *who, AIConcept_t concept, AI_Response *response ); // when an Actor speaks a concept
virtual void OnWeaponFired( CBaseCombatCharacter *whoFired, CBaseCombatWeapon *weapon ); // when someone fires a weapon
/**
* Debugging
*/
bool IsDebugging( unsigned int type ) const; // return true if debugging system is on for the given type(s)
void SetDebugTypes( NextBotDebugType type ); // start displaying debug info of the given type(s)
void DebugFilterAdd( int index ); // add given entindex to the debug filter
void DebugFilterAdd( const char *name ); // add given name to the debug filter
void DebugFilterRemove( int index ); // remove given entindex from the debug filter
void DebugFilterRemove( const char *name ); // remove given name from the debug filter
void DebugFilterClear( void ); // clear the debug filter (remove all entries)
bool IsDebugFilterMatch( const INextBot *bot ) const; // return true if the given bot matches the debug filter
void Select( INextBot *bot ); // mark bot as selected for further operations
void DeselectAll( void );
INextBot *GetSelected( void ) const;
INextBot *GetBotUnderCrosshair( CBasePlayer *picker ); // Get the bot under the given player's crosshair
//
// Put these in a derived class
//
void OnSurvivorVomitedUpon( CTerrorPlayer *victim ); // when a Survivor has been hit by Boomer Vomit
private:
friend class INextBot;
int Register( INextBot *bot );
void UnRegister( INextBot *bot );
CUtlLinkedList< INextBot * > m_botList; // list of all active NextBots
int m_iUpdateTickrate;
double m_CurUpdateStartTime;
double m_SumFrameTime;
unsigned int m_debugType; // debug flags
struct DebugFilter
{
int index; // entindex
enum { MAX_DEBUG_NAME_SIZE = 128 };
char name[ MAX_DEBUG_NAME_SIZE ];
};
CUtlVector< DebugFilter > m_debugFilterList;
INextBot *m_selectedBot; // selected bot for further debug operations
};
inline int NextBotManager::GetNextBotCount( void ) const
{
return m_botList.Count();
}
inline bool NextBotManager::IsDebugging( unsigned int type ) const
{
if ( type & m_debugType )
{
return true;
}
return false;
}
inline void NextBotManager::SetDebugTypes( NextBotDebugType type )
{
m_debugType = (unsigned int)type;
}
inline void NextBotManager::Select( INextBot *bot )
{
m_selectedBot = bot;
}
inline void NextBotManager::DeselectAll( void )
{
m_selectedBot = NULL;
}
inline INextBot *NextBotManager::GetSelected( void ) const
{
return m_selectedBot;
}
// singleton accessor
extern NextBotManager &TheNextBots( void );
#endif // _NEXT_BOT_MANAGER_H_
+243
View File
@@ -0,0 +1,243 @@
// NextBotUtil.h
// Utilities for the NextBot system
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_UTIL_H_
#define _NEXT_BOT_UTIL_H_
#include "NextBotLocomotionInterface.h"
//--------------------------------------------------------------------------------------------
/**
* A simple filter interface for various NextBot queries
*/
class INextBotEntityFilter
{
public:
// return true if the given entity passes this filter
virtual bool IsAllowed( CBaseEntity *entity ) const = 0;
};
// trace filter callback functions. needed for use with the querycache/optimization functionality
bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask );
bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask );
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips all players and NextBots
*/
class NextBotTraceFilterIgnoreActors : public CTraceFilterSimple
{
public:
NextBotTraceFilterIgnoreActors( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup, IgnoreActorsTraceFilterFunction )
{
}
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips all players, NextBots, and non-LOS blockers
*/
class NextBotVisionTraceFilter : public CTraceFilterSimple
{
public:
NextBotVisionTraceFilter( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup, VisionTraceFilterFunction )
{
}
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips all NextBots, but includes Players
*/
class NextBotTraceFilterIgnoreNextBots : public CTraceFilterSimple
{
public:
NextBotTraceFilterIgnoreNextBots( const IHandleEntity *passentity, int collisionGroup )
: CTraceFilterSimple( passentity, collisionGroup )
{
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
#ifdef TERROR
CBasePlayer *player = ToBasePlayer( entity );
if ( player && player->IsGhost() )
return false;
#endif // TERROR
return ( entity->MyNextBotPointer() == NULL );
}
return false;
}
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that obeys INextBot::IsAbleToBlockMovementOf()
*/
class NextBotTraceFilter : public CTraceFilterSimple
{
public:
NextBotTraceFilter( const IHandleEntity *passentity, int collisionGroup )
: CTraceFilterSimple( passentity, collisionGroup )
{
CBaseEntity *entity = const_cast<CBaseEntity *>(EntityFromEntityHandle( passentity ));
m_passBot = entity->MyNextBotPointer();
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
#ifdef TERROR
CBasePlayer *player = ToBasePlayer( entity );
if ( player && player->IsGhost() )
return false;
#endif // TERROR
// Skip players on the same team - they're not solid to us, and we'll avoid them
if ( entity->IsPlayer() && m_passBot && m_passBot->GetEntity() &&
m_passBot->GetEntity()->GetTeamNumber() == entity->GetTeamNumber() )
return false;
INextBot *bot = entity->MyNextBotPointer();
return ( !bot || bot->IsAbleToBlockMovementOf( m_passBot ) );
}
return false;
}
const INextBot *m_passBot;
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that only hits players and NextBots
*/
class NextBotTraceFilterOnlyActors : public CTraceFilterSimple
{
public:
NextBotTraceFilterOnlyActors( const IHandleEntity *passentity, int collisionGroup )
: CTraceFilterSimple( passentity, collisionGroup )
{
}
virtual TraceType_t GetTraceType() const
{
return TRACE_ENTITIES_ONLY;
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
#ifdef TERROR
CBasePlayer *player = ToBasePlayer( entity );
if ( player && player->IsGhost() )
return false;
#endif // TERROR
return ( entity->MyNextBotPointer() || entity->IsPlayer() );
}
return false;
}
};
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips "traversable" entities. The "when" argument creates
* a temporal context for asking if an entity is IMMEDIATELY traversable (like thin
* glass that just breaks as we walk through it) or EVENTUALLY traversable (like a
* breakable object that will take some time to break through)
*/
class NextBotTraversableTraceFilter : public CTraceFilterSimple
{
public:
NextBotTraversableTraceFilter( INextBot *bot, ILocomotion::TraverseWhenType when = ILocomotion::EVENTUALLY ) : CTraceFilterSimple( bot->GetEntity(), COLLISION_GROUP_NONE )
{
m_bot = bot;
m_when = when;
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
if ( m_bot->IsSelf( entity ) )
{
return false;
}
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
return !m_bot->GetLocomotionInterface()->IsEntityTraversable( entity, m_when );
}
return false;
}
private:
INextBot *m_bot;
ILocomotion::TraverseWhenType m_when;
};
#ifdef OBSOLETE
//--------------------------------------------------------------------------------------------
/**
* Trace filter that skips "traversable" entities, but hits other Actors.
* Used for obstacle avoidance.
*/
class NextBotMovementAvoidanceTraceFilter : public CTraceFilterSimple
{
public:
NextBotMovementAvoidanceTraceFilter( INextBot *bot ) : CTraceFilterSimple( bot->GetEntity(), COLLISION_GROUP_NONE )
{
m_bot = bot;
}
virtual bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
#ifdef TERROR
CBasePlayer *player = ToBasePlayer( entity );
if ( player && player->IsGhost() )
return false;
#endif // TERROR
if ( m_bot->IsSelf( entity ) )
{
return false;
}
if ( CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask ) )
{
return !m_bot->GetLocomotionInterface()->IsEntityTraversable( entity, ILocomotion::IMMEDIATELY );
}
return false;
}
private:
INextBot *m_bot;
};
#endif
#endif // _NEXT_BOT_UTIL_H_
@@ -0,0 +1,729 @@
// NextBotVisionInterface.cpp
// Implementation of common vision system
// Author: Michael Booth, May 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "nav.h"
#include "functorutils.h"
#include "NextBot.h"
#include "NextBotVisionInterface.h"
#include "NextBotBodyInterface.h"
#include "NextBotUtil.h"
#ifdef TERROR
#include "querycache.h"
#endif
#include "tier0/vprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar nb_blind( "nb_blind", "0", FCVAR_CHEAT, "Disable vision" );
ConVar nb_debug_known_entities( "nb_debug_known_entities", "0", FCVAR_CHEAT, "Show the 'known entities' for the bot that is the current spectator target" );
//------------------------------------------------------------------------------------------
IVision::IVision( INextBot *bot ) : INextBotComponent( bot )
{
Reset();
}
//------------------------------------------------------------------------------------------
/**
* Reset to initial state
*/
void IVision::Reset( void )
{
INextBotComponent::Reset();
m_knownEntityVector.RemoveAll();
m_lastVisionUpdateTimestamp = 0.0f;
m_FOV = GetDefaultFieldOfView();
m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
for( int i=0; i<MAX_TEAMS; ++i )
{
m_notVisibleTimer[i].Invalidate();
}
}
//------------------------------------------------------------------------------------------
/**
* Ask the current behavior to select the most dangerous threat from
* our set of currently known entities
* TODO: Find a semantically better place for this to live.
*/
const CKnownEntity *IVision::GetPrimaryKnownThreat( bool onlyVisibleThreats ) const
{
if ( m_knownEntityVector.Count() == 0 )
return NULL;
const CKnownEntity *threat = NULL;
int i;
// find the first valid entity
for( i=0; i<m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &firstThreat = m_knownEntityVector[i];
// check in case status changes between updates
if ( IsAwareOf( firstThreat ) && !firstThreat.IsObsolete() && !IsIgnored( firstThreat.GetEntity() ) && GetBot()->IsEnemy( firstThreat.GetEntity() ) )
{
if ( !onlyVisibleThreats || firstThreat.IsVisible() )
{
threat = &firstThreat;
break;
}
}
}
if ( threat == NULL )
return NULL;
for( ++i; i<m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &newThreat = m_knownEntityVector[i];
// check in case status changes between updates
if ( IsAwareOf( newThreat ) && !newThreat.IsObsolete() && !IsIgnored( newThreat.GetEntity() ) && GetBot()->IsEnemy( newThreat.GetEntity() ) )
{
if ( !onlyVisibleThreats || newThreat.IsVisible() )
{
threat = GetBot()->GetIntentionInterface()->SelectMoreDangerousThreat( GetBot(), GetBot()->GetEntity(), threat, &newThreat );
}
}
}
return threat;
}
//------------------------------------------------------------------------------------------
/**
* Return the closest recognized entity
*/
const CKnownEntity *IVision::GetClosestKnown( int team ) const
{
const Vector &myPos = GetBot()->GetPosition();
const CKnownEntity *close = NULL;
float closeRange = 999999999.9f;
for( int i=0; i < m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &known = m_knownEntityVector[i];
if ( !known.IsObsolete() && IsAwareOf( known ) )
{
if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
{
Vector to = known.GetLastKnownPosition() - myPos;
float rangeSq = to.LengthSqr();
if ( rangeSq < closeRange )
{
close = &known;
closeRange = rangeSq;
}
}
}
}
return close;
}
//------------------------------------------------------------------------------------------
/**
* Return the closest recognized entity that passes the given filter
*/
const CKnownEntity *IVision::GetClosestKnown( const INextBotEntityFilter &filter ) const
{
const Vector &myPos = GetBot()->GetPosition();
const CKnownEntity *close = NULL;
float closeRange = 999999999.9f;
for( int i=0; i < m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &known = m_knownEntityVector[i];
if ( !known.IsObsolete() && IsAwareOf( known ) )
{
if ( filter.IsAllowed( known.GetEntity() ) )
{
Vector to = known.GetLastKnownPosition() - myPos;
float rangeSq = to.LengthSqr();
if ( rangeSq < closeRange )
{
close = &known;
closeRange = rangeSq;
}
}
}
}
return close;
}
//------------------------------------------------------------------------------------------
/**
* Given an entity, return our known version of it (or NULL if we don't know of it)
*/
const CKnownEntity *IVision::GetKnown( const CBaseEntity *entity ) const
{
if ( entity == NULL )
return NULL;
for( int i=0; i < m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &known = m_knownEntityVector[i];
if ( known.GetEntity() && known.GetEntity()->entindex() == entity->entindex() )
{
return &known;
}
}
return NULL;
}
//------------------------------------------------------------------------------------------
/**
* Introduce a known entity into the system. Its position is assumed to be known
* and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
* of known entities by being told about them, hearing them, etc.
*/
void IVision::AddKnownEntity( CBaseEntity *entity )
{
CKnownEntity known( entity );
// only add it if we don't already know of it
if ( m_knownEntityVector.Find( known ) == m_knownEntityVector.InvalidIndex() )
{
m_knownEntityVector.AddToTail( known );
}
}
//------------------------------------------------------------------------------------------
/**
* Return the number of entity on the given team known to us closer than rangeLimit
*/
int IVision::GetKnownCount( int team, bool onlyVisible, float rangeLimit ) const
{
int count = 0;
FOR_EACH_VEC( m_knownEntityVector, it )
{
const CKnownEntity &known = m_knownEntityVector[ it ];
if ( !known.IsObsolete() && IsAwareOf( known ) )
{
if ( team == TEAM_ANY || known.GetEntity()->GetTeamNumber() == team )
{
if ( !onlyVisible || known.IsVisible() )
{
if ( rangeLimit < 0.0f || GetBot()->IsRangeLessThan( known.GetLastKnownPosition(), rangeLimit ) )
{
++count;
}
}
}
}
}
return count;
}
//------------------------------------------------------------------------------------------
class PopulateVisibleVector
{
public:
PopulateVisibleVector( CUtlVector< CBaseEntity * > *potentiallyVisible )
{
m_potentiallyVisible = potentiallyVisible;
}
bool operator() ( CBaseEntity *actor )
{
m_potentiallyVisible->AddToTail( actor );
return true;
}
CUtlVector< CBaseEntity * > *m_potentiallyVisible;
};
//------------------------------------------------------------------------------------------
/**
* Populate "potentiallyVisible" with the set of all entities we could potentially see.
* Entities in this set will be tested for visibility/recognition in IVision::Update()
*/
void IVision::CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible )
{
potentiallyVisible->RemoveAll();
// by default, only consider players and other bots as potentially visible
PopulateVisibleVector populate( potentiallyVisible );
ForEachActor( populate );
}
//------------------------------------------------------------------------------------------
class CollectVisible
{
public:
CollectVisible( IVision *vision )
{
m_vision = vision;
}
bool operator() ( CBaseEntity *entity )
{
if ( entity &&
!m_vision->IsIgnored( entity ) &&
entity->IsAlive() &&
entity != m_vision->GetBot()->GetEntity() &&
m_vision->IsAbleToSee( entity, IVision::USE_FOV ) )
{
m_recognized.AddToTail( entity );
}
return true;
}
bool Contains( CBaseEntity *entity ) const
{
for( int i=0; i < m_recognized.Count(); ++i )
{
if ( entity->entindex() == m_recognized[ i ]->entindex() )
{
return true;
}
}
return false;
}
IVision *m_vision;
CUtlVector< CBaseEntity * > m_recognized;
};
//------------------------------------------------------------------------------------------
void IVision::UpdateKnownEntities( void )
{
VPROF_BUDGET( "IVision::UpdateKnownEntities", "NextBot" );
// construct set of potentially visible objects
CUtlVector< CBaseEntity * > potentiallyVisible;
CollectPotentiallyVisibleEntities( &potentiallyVisible );
// collect set of visible and recognized entities at this moment
CollectVisible visibleNow( this );
FOR_EACH_VEC( potentiallyVisible, pit )
{
VPROF_BUDGET( "IVision::UpdateKnownEntities( collect visible )", "NextBot" );
if ( visibleNow( potentiallyVisible[ pit ] ) == false )
break;
}
// update known set with new data
{ VPROF_BUDGET( "IVision::UpdateKnownEntities( update status )", "NextBot" );
int i;
for( i=0; i < m_knownEntityVector.Count(); ++i )
{
CKnownEntity &known = m_knownEntityVector[i];
// clear out obsolete knowledge
if ( known.GetEntity() == NULL || known.IsObsolete() )
{
m_knownEntityVector.Remove( i );
--i;
continue;
}
if ( visibleNow.Contains( known.GetEntity() ) )
{
// this visible entity was already known (but perhaps not visible until now)
known.UpdatePosition();
known.UpdateVisibilityStatus( true );
// has our reaction time just elapsed?
if ( gpGlobals->curtime - known.GetTimeWhenBecameVisible() >= GetMinRecognizeTime() &&
m_lastVisionUpdateTimestamp - known.GetTimeWhenBecameVisible() < GetMinRecognizeTime() )
{
if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
{
ConColorMsg( Color( 0, 255, 0, 255 ), "%3.2f: %s caught sight of %s(#%d)\n",
gpGlobals->curtime,
GetBot()->GetDebugIdentifier(),
known.GetEntity()->GetClassname(),
known.GetEntity()->entindex() );
NDebugOverlay::Line( GetBot()->GetBodyInterface()->GetEyePosition(), known.GetLastKnownPosition(), 255, 255, 0, false, 0.2f );
}
GetBot()->OnSight( known.GetEntity() );
}
// restart 'not seen' timer
m_notVisibleTimer[ known.GetEntity()->GetTeamNumber() ].Start();
}
else // known entity is not currently visible
{
if ( known.IsVisible() )
{
// previously known and visible entity is now no longer visible
known.UpdateVisibilityStatus( false );
// lost sight of this entity
if ( GetBot()->IsDebugging( NEXTBOT_VISION ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Lost sight of %s(#%d)\n",
gpGlobals->curtime,
GetBot()->GetDebugIdentifier(),
known.GetEntity()->GetClassname(),
known.GetEntity()->entindex() );
}
GetBot()->OnLostSight( known.GetEntity() );
}
if ( !known.HasLastKnownPositionBeenSeen() )
{
// can we see the entity's last know position?
if ( IsAbleToSee( known.GetLastKnownPosition(), IVision::USE_FOV ) )
{
known.MarkLastKnownPositionAsSeen();
}
}
}
}
}
// check for new recognizes that were not in the known set
{ VPROF_BUDGET( "IVision::UpdateKnownEntities( new recognizes )", "NextBot" );
int i, j;
for( i=0; i < visibleNow.m_recognized.Count(); ++i )
{
for( j=0; j < m_knownEntityVector.Count(); ++j )
{
if ( visibleNow.m_recognized[i] == m_knownEntityVector[j].GetEntity() )
{
break;
}
}
if ( j == m_knownEntityVector.Count() )
{
// recognized a previously unknown entity (emit OnSight() event after reaction time has passed)
CKnownEntity known( visibleNow.m_recognized[i] );
known.UpdatePosition();
known.UpdateVisibilityStatus( true );
m_knownEntityVector.AddToTail( known );
}
}
}
// debugging
if ( nb_debug_known_entities.GetBool() )
{
CBasePlayer *watcher = UTIL_GetListenServerHost();
if ( watcher )
{
CBaseEntity *subject = watcher->GetObserverTarget();
if ( subject && GetBot()->IsSelf( subject ) )
{
for( int i=0; i < m_knownEntityVector.Count(); ++i )
{
CKnownEntity &known = m_knownEntityVector[i];
if ( GetBot()->IsFriend( known.GetEntity() ) )
{
if ( known.IsVisible() )
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 0, 255, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
else
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 0, 100, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
else
{
if ( known.IsVisible() )
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 5.0f, 255, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
else
NDebugOverlay::HorzArrow( GetBot()->GetEntity()->GetAbsOrigin(), known.GetLastKnownPosition(), 2.0f, 100, 0, 0, 255, true, NDEBUG_PERSIST_TILL_NEXT_SERVER );
}
}
}
}
}
}
//------------------------------------------------------------------------------------------
/**
* Update internal state
*/
void IVision::Update( void )
{
VPROF_BUDGET( "IVision::Update", "NextBotExpensive" );
// throttle update rate
if ( !m_scanTimer.IsElapsed() )
{
return;
}
m_scanTimer.Start( 0.5f * GetMinRecognizeTime() );
if ( nb_blind.GetBool() )
{
m_knownEntityVector.RemoveAll();
return;
}
UpdateKnownEntities();
m_lastVisionUpdateTimestamp = gpGlobals->curtime;
}
//------------------------------------------------------------------------------------------
bool IVision::IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot ) const
{
VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
if ( GetBot()->IsRangeGreaterThan( subject, GetMaxVisionRange() ) )
{
return false;
}
if ( GetBot()->GetEntity()->IsHiddenByFog( subject ) )
{
// lost in the fog
return false;
}
if ( checkFOV == USE_FOV && !IsInFieldOfView( subject ) )
{
return false;
}
if ( !IsNoticed( subject ) )
{
return false;
}
// do actual line-of-sight trace
return IsLineOfSightClearToEntity( subject );
}
//------------------------------------------------------------------------------------------
bool IVision::IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const
{
VPROF_BUDGET( "IVision::IsAbleToSee", "NextBotExpensive" );
if ( GetBot()->IsRangeGreaterThan( pos, GetMaxVisionRange() ) )
{
return false;
}
if ( GetBot()->GetEntity()->IsHiddenByFog( pos ) )
{
// lost in the fog
return false;
}
if ( checkFOV == USE_FOV && !IsInFieldOfView( pos ) )
{
return false;
}
// do actual line-of-sight trace
return IsLineOfSightClear( pos );
}
//------------------------------------------------------------------------------------------
/**
* Angle given in degrees
*/
void IVision::SetFieldOfView( float horizAngle )
{
m_FOV = horizAngle;
m_cosHalfFOV = cos( 0.5f * m_FOV * M_PI / 180.0f );
}
//------------------------------------------------------------------------------------------
bool IVision::IsInFieldOfView( const Vector &pos ) const
{
#ifdef CHECK_OLD_CODE_AGAINST_NEW
bool bCheck = PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
to.NormalizeInPlace();
float cosDiff = DotProduct( GetBot()->GetBodyInterface()->GetViewVector(), to );
if ( ( cosDiff > m_cosHalfFOV ) != bCheck )
{
Assert(0);
bool bCheck2 =
PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
}
return ( cosDiff > m_cosHalfFOV );
#else
return PointWithinViewAngle( GetBot()->GetBodyInterface()->GetEyePosition(), pos, GetBot()->GetBodyInterface()->GetViewVector(), m_cosHalfFOV );
#endif
return true;
}
//------------------------------------------------------------------------------------------
bool IVision::IsInFieldOfView( CBaseEntity *subject ) const
{
/// @todo check more points
if ( IsInFieldOfView( subject->WorldSpaceCenter() ) )
{
return true;
}
return IsInFieldOfView( subject->EyePosition() );
}
//------------------------------------------------------------------------------------------
/**
* Return true if the ray to the given point is unobstructed
*/
bool IVision::IsLineOfSightClear( const Vector &pos ) const
{
VPROF_BUDGET( "IVision::IsLineOfSightClear", "NextBot" );
VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClear", 1 );
trace_t result;
NextBotVisionTraceFilter filter( GetBot()->GetEntity(), COLLISION_GROUP_NONE );
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), pos, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
return ( result.fraction >= 1.0f && !result.startsolid );
}
//------------------------------------------------------------------------------------------
bool IVision::IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot ) const
{
#ifdef TERROR
// TODO: Integration querycache & its dependencies
VPROF_INCREMENT_COUNTER( "IVision::IsLineOfSightClearToEntity", 1 );
VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBotSpiky" );
bool bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetBodyInterface()->GetEntity(), EOFFSET_MODE_EYEPOSITION,
subject, EOFFSET_MODE_WORLDSPACE_CENTER,
subject, COLLISION_GROUP_NONE,
MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, VisionTraceFilterFunction, 1.0 );
#ifdef USE_NON_CACHE_QUERY
trace_t result;
NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
Assert( result.DidHit() != bClear );
if ( subject->IsPlayer() && ! bClear )
{
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
bClear = IsLineOfSightBetweenTwoEntitiesClear( GetBot()->GetEntity(),
EOFFSET_MODE_EYEPOSITION,
subject, EOFFSET_MODE_EYEPOSITION,
subject, COLLISION_GROUP_NONE,
MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE,
IgnoreActorsTraceFilterFunction, 1.0 );
// this WILL assert - the query interface happens at a different time, and has hysteresis.
Assert( result.DidHit() != bClear );
}
#endif
return bClear;
#else
// TODO: Use plain-old traces until querycache/etc gets integrated
VPROF_BUDGET( "IVision::IsLineOfSightClearToEntity", "NextBot" );
trace_t result;
NextBotTraceFilterIgnoreActors filter( subject, COLLISION_GROUP_NONE );
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->WorldSpaceCenter(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
if ( result.DidHit() )
{
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->EyePosition(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
if ( result.DidHit() )
{
UTIL_TraceLine( GetBot()->GetBodyInterface()->GetEyePosition(), subject->GetAbsOrigin(), MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, &filter, &result );
}
}
if ( visibleSpot )
{
*visibleSpot = result.endpos;
}
return ( result.fraction >= 1.0f && !result.startsolid );
#endif
}
//------------------------------------------------------------------------------------------
/**
* Are we looking directly at the given position
*/
bool IVision::IsLookingAt( const Vector &pos, float cosTolerance ) const
{
Vector to = pos - GetBot()->GetBodyInterface()->GetEyePosition();
to.NormalizeInPlace();
Vector forward;
AngleVectors( GetBot()->GetEntity()->EyeAngles(), &forward );
return DotProduct( to, forward ) > cosTolerance;
}
//------------------------------------------------------------------------------------------
/**
* Are we looking directly at the given actor
*/
bool IVision::IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance ) const
{
return IsLookingAt( actor->EyePosition(), cosTolerance );
}
@@ -0,0 +1,202 @@
// NextBotVisionInterface.h
// Visual information query interface for bots
// Author: Michael Booth, April 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_VISION_INTERFACE_H_
#define _NEXT_BOT_VISION_INTERFACE_H_
#include "NextBotComponentInterface.h"
#include "NextBotKnownEntity.h"
class IBody;
class INextBotEntityFilter;
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for HOW the bot sees (near sighted? night vision? etc)
*/
class IVision : public INextBotComponent
{
public:
IVision( INextBot *bot );
virtual ~IVision() { }
virtual void Reset( void ); // reset to initial state
virtual void Update( void ); // update internal state
//-- attention/short term memory interface follows ------------------------------------------
/**
* Iterate each interesting entity we are aware of.
* If functor returns false, stop iterating and return false.
* NOTE: known.GetEntity() is guaranteed to be non-NULL
*/
class IForEachKnownEntity
{
public:
virtual bool Inspect( const CKnownEntity &known ) = 0;
};
virtual bool ForEachKnownEntity( IForEachKnownEntity &func );
virtual const CKnownEntity *GetPrimaryKnownThreat( bool onlyVisibleThreats = false ) const; // return the biggest threat to ourselves that we are aware of
virtual float GetTimeSinceVisible( int team ) const; // return time since we saw any member of the given team
virtual const CKnownEntity *GetClosestKnown( int team = TEAM_ANY ) const; // return the closest known entity
virtual int GetKnownCount( int team, bool onlyVisible = false, float rangeLimit = -1.0f ) const; // return the number of entities on the given team known to us closer than rangeLimit
virtual const CKnownEntity *GetClosestKnown( const INextBotEntityFilter &filter ) const; // return the closest known entity that passes the given filter
virtual const CKnownEntity *GetKnown( const CBaseEntity *entity ) const; // given an entity, return our known version of it (or NULL if we don't know of it)
// Introduce a known entity into the system. Its position is assumed to be known
// and will be updated, and it is assumed to not yet have been seen by us, allowing for learning
// of known entities by being told about them, hearing them, etc.
virtual void AddKnownEntity( CBaseEntity *entity );
//-- physical vision interface follows ------------------------------------------------------
/**
* Populate "potentiallyVisible" with the set of all entities we could potentially see.
* Entities in this set will be tested for visibility/recognition in IVision::Update()
*/
virtual void CollectPotentiallyVisibleEntities( CUtlVector< CBaseEntity * > *potentiallyVisible );
virtual float GetMaxVisionRange( void ) const; // return maximum distance vision can reach
virtual float GetMinRecognizeTime( void ) const; // return VISUAL reaction time
/**
* IsAbleToSee() returns true if the viewer can ACTUALLY SEE the subject or position,
* taking into account blindness, smoke effects, invisibility, etc.
* If 'visibleSpot' is non-NULL, the highest priority spot on the subject that is visible is returned.
*/
enum FieldOfViewCheckType { USE_FOV, DISREGARD_FOV };
virtual bool IsAbleToSee( CBaseEntity *subject, FieldOfViewCheckType checkFOV, Vector *visibleSpot = NULL ) const;
virtual bool IsAbleToSee( const Vector &pos, FieldOfViewCheckType checkFOV ) const;
virtual bool IsNoticed( CBaseEntity *subject ) const; // return true if we 'notice' the subject, even if we have clear LOS
virtual bool IsIgnored( CBaseEntity *subject ) const; // return true to completely ignore this entity
/**
* Check if 'subject' is within the viewer's field of view
*/
virtual bool IsInFieldOfView( const Vector &pos ) const;
virtual bool IsInFieldOfView( CBaseEntity *subject ) const;
virtual float GetDefaultFieldOfView( void ) const; // return default FOV in degrees
virtual float GetFieldOfView( void ) const; // return FOV in degrees
virtual void SetFieldOfView( float horizAngle ); // angle given in degrees
virtual bool IsLineOfSightClear( const Vector &pos ) const; // return true if the ray to the given point is unobstructed
/**
* Returns true if the ray between the position and the subject is unobstructed.
* A visible spot on the subject is returned in 'visibleSpot'.
*/
virtual bool IsLineOfSightClearToEntity( const CBaseEntity *subject, Vector *visibleSpot = NULL ) const;
/// @todo: Implement LookAt system
virtual bool IsLookingAt( const Vector &pos, float cosTolerance = 0.95f ) const; // are we looking at the given position
virtual bool IsLookingAt( const CBaseCombatCharacter *actor, float cosTolerance = 0.95f ) const; // are we looking at the given actor
private:
CountdownTimer m_scanTimer; // for throttling update rate
float m_FOV; // current FOV in degrees
float m_cosHalfFOV; // the cosine of FOV/2
CUtlVector< CKnownEntity > m_knownEntityVector; // the set of enemies/friends we are aware of
void UpdateKnownEntities( void );
bool IsAwareOf( const CKnownEntity &known ) const; // return true if our reaction time has passed for this entity
float m_lastVisionUpdateTimestamp;
IntervalTimer m_notVisibleTimer[ MAX_TEAMS ]; // for tracking interval since last saw a member of the given team
};
inline float IVision::GetDefaultFieldOfView( void ) const
{
return 90.0f;
}
inline float IVision::GetFieldOfView( void ) const
{
return m_FOV;
}
inline float IVision::GetTimeSinceVisible( int team ) const
{
if ( team == TEAM_ANY )
{
// return minimum time
float time = 9999999999.9f;
for( int i=0; i<MAX_TEAMS; ++i )
{
if ( m_notVisibleTimer[i].HasStarted() )
{
if ( time > m_notVisibleTimer[i].GetElapsedTime() )
{
team = m_notVisibleTimer[i].GetElapsedTime();
}
}
}
return time;
}
if ( team >= 0 && team < MAX_TEAMS )
{
return m_notVisibleTimer[ team ].GetElapsedTime();
}
return 0.0f;
}
inline bool IVision::IsAwareOf( const CKnownEntity &known ) const
{
return known.GetTimeSinceBecameKnown() >= GetMinRecognizeTime();
}
inline bool IVision::ForEachKnownEntity( IVision::IForEachKnownEntity &func )
{
for( int i=0; i<m_knownEntityVector.Count(); ++i )
{
const CKnownEntity &known = m_knownEntityVector[i];
if ( !known.IsObsolete() && IsAwareOf( known ) )
{
if ( func.Inspect( known ) == false )
{
return false;
}
}
}
return true;
}
inline bool IVision::IsNoticed( CBaseEntity *subject ) const
{
return true;
}
inline bool IVision::IsIgnored( CBaseEntity *subject ) const
{
return false;
}
inline float IVision::GetMaxVisionRange( void ) const
{
return 2000.0f;
}
inline float IVision::GetMinRecognizeTime( void ) const
{
return 0.0f;
}
#endif // _NEXT_BOT_VISION_INTERFACE_H_
@@ -0,0 +1,166 @@
//========== Copyright © 2008, Valve Corporation, All rights reserved. ========
//
// Purpose:
//
//=============================================================================
#include "cbase.h"
#include "NextBotChasePath.h"
#include "tier1/fmtstr.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//----------------------------------------------------------------------------------------------
/**
* Try to cutoff our chase subject
*/
Vector ChasePath::PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const
{
ILocomotion *mover = bot->GetLocomotionInterface();
const Vector &subjectPos = subject->GetAbsOrigin();
Vector to = subjectPos - bot->GetPosition();
to.z = 0.0f;
float flRangeSq = to.LengthSqr();
// don't lead if subject is very far away
float flLeadRadiusSq = GetLeadRadius();
flLeadRadiusSq *= flLeadRadiusSq;
if ( flRangeSq > flLeadRadiusSq )
return subjectPos;
// Normalize in place
float range = sqrt( flRangeSq );
to /= ( range + 0.0001f ); // avoid divide by zero
// estimate time to reach subject, assuming maximum speed
float leadTime = 0.5f + ( range / ( mover->GetRunSpeed() + 0.0001f ) );
// estimate amount to lead the subject
Vector lead = leadTime * subject->GetAbsVelocity();
lead.z = 0.0f;
if ( DotProduct( to, lead ) < 0.0f )
{
// the subject is moving towards us - only pay attention
// to his perpendicular velocity for leading
Vector2D to2D = to.AsVector2D();
to2D.NormalizeInPlace();
Vector2D perp( -to2D.y, to2D.x );
float enemyGroundSpeed = lead.x * perp.x + lead.y * perp.y;
lead.x = enemyGroundSpeed * perp.x;
lead.y = enemyGroundSpeed * perp.y;
}
// compute our desired destination
Vector pathTarget = subjectPos + lead;
// validate this destination
// don't lead through walls
if ( lead.LengthSqr() > 36.0f )
{
float fraction;
if ( !mover->IsPotentiallyTraversable( subjectPos, pathTarget, ILocomotion::IMMEDIATELY, &fraction ) )
{
// tried to lead through an unwalkable area - clip to walkable space
pathTarget = subjectPos + fraction * ( pathTarget - subjectPos );
}
}
// don't lead over cliffs
CNavArea *leadArea = NULL;
#ifdef NEED_GPGLOBALS_SERVERCOUNT_TO_DO_THIS
CBaseCombatCharacter *pBCC = subject->MyCombatCharacterPointer();
if ( pBCC && CloseEnough( pathTarget, subjectPos, 3.0 ) )
{
pathTarget = subjectPos;
leadArea = pBCC->GetLastKnownArea(); // can return null?
}
else
{
struct CacheEntry_t
{
CacheEntry_t() : pArea(NULL) {}
Vector target;
CNavArea *pArea;
};
static int iServer;
static CacheEntry_t cache[4];
static int iNext;
int i;
bool bFound = false;
if ( iServer != gpGlobals->serverCount )
{
for ( i = 0; i < ARRAYSIZE(cache); i++ )
{
cache[i].pArea = NULL;
}
iServer = gpGlobals->serverCount;
}
else
{
for ( i = 0; i < ARRAYSIZE(cache); i++ )
{
if ( cache[i].pArea && CloseEnough( cache[i].target, pathTarget, 2.0 ) )
{
pathTarget = cache[i].target;
leadArea = cache[i].pArea;
bFound = true;
break;
}
}
}
if ( !bFound )
{
leadArea = TheNavMesh->GetNearestNavArea( pathTarget );
if ( leadArea )
{
cache[iNext].target = pathTarget;
cache[iNext].pArea = leadArea;
iNext = ( iNext + 1 ) % ARRAYSIZE( cache );
}
}
}
#else
leadArea = TheNavMesh->GetNearestNavArea( pathTarget );
#endif
if ( !leadArea || leadArea->GetZ( pathTarget.x, pathTarget.y ) < pathTarget.z - mover->GetMaxJumpHeight() )
{
// would fall off a cliff
return subjectPos;
}
/** This needs more thought - it is preventing bots from using dropdowns
if ( mover->HasPotentialGap( subjectPos, pathTarget, &fraction ) )
{
// tried to lead over a cliff - clip to safe region
pathTarget = subjectPos + fraction * ( pathTarget - subjectPos );
}
*/
return pathTarget;
}
// if the victim is a player, poke them so they know they're being chased
void DirectChasePath::NotifyVictim( INextBot *me, CBaseEntity *victim )
{
CBaseCombatCharacter *pBCCVictim = ToBaseCombatCharacter( victim );
if ( !pBCCVictim )
return;
pBCCVictim->OnPursuedBy( me );
}
+376
View File
@@ -0,0 +1,376 @@
// NextBotChasePath.h
// Maintain and follow a "chase path" to a selected Actor
// Author: Michael Booth, September 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_CHASE_PATH_
#define _NEXT_BOT_CHASE_PATH_
#include "nav.h"
#include "NextBotInterface.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotChasePath.h"
#include "NextBotUtil.h"
#include "NextBotPathFollow.h"
#include "tier0/vprof.h"
//----------------------------------------------------------------------------------------------
/**
* A ChasePath extends a PathFollower to periodically recompute a path to a chase
* subject, and to move along the path towards that subject.
*/
class ChasePath : public PathFollower
{
public:
enum SubjectChaseType
{
LEAD_SUBJECT,
DONT_LEAD_SUBJECT
};
ChasePath( SubjectChaseType chaseHow = DONT_LEAD_SUBJECT );
virtual ~ChasePath() { }
virtual void Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos = NULL ); // update path to chase target and move bot along path
virtual float GetLeadRadius( void ) const; // range where movement leading begins - beyond this just head right for the subject
virtual float GetMaxPathLength( void ) const; // return maximum path length
virtual Vector PredictSubjectPosition( INextBot *bot, CBaseEntity *subject ) const; // try to cutoff our chase subject, knowing our relative positions and velocities
virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const; // return true if situation has changed enough to warrant recomputing the current path
virtual float GetLifetime( void ) const; // Return duration this path is valid. Path will become invalid at its earliest opportunity once this duration elapses. Zero = infinite lifetime
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
private:
void RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos );
CountdownTimer m_failTimer; // throttle re-pathing if last path attempt failed
CountdownTimer m_throttleTimer; // require a minimum time between re-paths
CountdownTimer m_lifetimeTimer;
EHANDLE m_lastPathSubject; // the subject used to compute the current/last path
SubjectChaseType m_chaseHow;
};
inline ChasePath::ChasePath( SubjectChaseType chaseHow )
{
m_failTimer.Invalidate();
m_throttleTimer.Invalidate();
m_lifetimeTimer.Invalidate();
m_lastPathSubject = NULL;
m_chaseHow = chaseHow;
}
inline float ChasePath::GetLeadRadius( void ) const
{
return 500.0f; // 1000.0f;
}
inline float ChasePath::GetMaxPathLength( void ) const
{
// no limit
return 0.0f;
}
inline float ChasePath::GetLifetime( void ) const
{
// infinite duration
return 0.0f;
}
inline void ChasePath::Invalidate( void )
{
// path is gone, repath at earliest opportunity
m_throttleTimer.Invalidate();
m_lifetimeTimer.Invalidate();
// extend
PathFollower::Invalidate();
}
//----------------------------------------------------------------------------------------------
/**
* Maintain a path to our chase subject and move along that path
*/
inline void ChasePath::Update( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos )
{
VPROF_BUDGET( "ChasePath::Update", "NextBot" );
// maintain the path to the subject
RefreshPath( bot, subject, cost, pPredictedSubjectPos );
// move along the path towards the subject
PathFollower::Update( bot );
}
//----------------------------------------------------------------------------------------------
/**
* Return true if situation has changed enough to warrant recomputing the current path
*/
inline bool ChasePath::IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const
{
// the closer we get, the more accurate our path needs to be
Vector to = subject->GetAbsOrigin() - bot->GetPosition();
const float minTolerance = 0.0f; // 25.0f;
const float toleranceRate = 0.33f; // 1.0f; // 0.15f;
float tolerance = minTolerance + toleranceRate * to.Length();
return ( subject->GetAbsOrigin() - GetEndPosition() ).IsLengthGreaterThan( tolerance );
}
//----------------------------------------------------------------------------------------------
/**
* Periodically rebuild the path to our victim
*/
inline void ChasePath::RefreshPath( INextBot *bot, CBaseEntity *subject, const IPathCost &cost, Vector *pPredictedSubjectPos )
{
VPROF_BUDGET( "ChasePath::RefreshPath", "NextBot" );
ILocomotion *mover = bot->GetLocomotionInterface();
// don't change our path if we're on a ladder
if ( IsValid() && mover->IsUsingLadder() )
{
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
// don't allow repath until a moment AFTER we have left the ladder
m_throttleTimer.Start( 1.0f );
return;
}
if ( subject == NULL )
{
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No subject.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
return;
}
if ( !m_failTimer.IsElapsed() )
{
// if ( bot->IsDebugging( NEXTBOT_PATH ) )
// {
// DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Fail timer not elapsed.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
// }
return;
}
// if our path subject changed, repath immediately
if ( subject != m_lastPathSubject )
{
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
DevMsg( "%3.2f: bot(#%d) Chase path subject changed (from %p to %p).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_lastPathSubject.Get(), subject );
}
Invalidate();
// new subject, fresh attempt
m_failTimer.Invalidate();
}
if ( IsValid() && !m_throttleTimer.IsElapsed() )
{
// require a minimum time between repaths, as long as we have a path to follow
// if ( bot->IsDebugging( NEXTBOT_PATH ) )
// {
// DevMsg( "%3.2f: bot(#%d) ChasePath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
// }
return;
}
if ( IsValid() && m_lifetimeTimer.HasStarted() && m_lifetimeTimer.IsElapsed() )
{
// this path's lifetime has elapsed
Invalidate();
}
if ( !IsValid() || IsRepathNeeded( bot, subject ) )
{
// the situation has changed - try a new path
bool isPath;
Vector pathTarget = subject->GetAbsOrigin();
if ( m_chaseHow == LEAD_SUBJECT )
{
pathTarget = pPredictedSubjectPos ? *pPredictedSubjectPos : PredictSubjectPosition( bot, subject );
isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() );
}
else if ( subject->MyCombatCharacterPointer() && subject->MyCombatCharacterPointer()->GetLastKnownArea() )
{
isPath = Compute( bot, subject->MyCombatCharacterPointer(), cost, GetMaxPathLength() );
}
else
{
isPath = Compute( bot, pathTarget, cost, GetMaxPathLength() );
}
if ( isPath )
{
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
const float size = 20.0f;
NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, RandomInt( 0, 200 ), 255, 255, true, 30.0f );
DevMsg( "%3.2f: bot(#%d) REPATH\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
m_lastPathSubject = subject;
const float minRepathInterval = 0.5f;
m_throttleTimer.Start( minRepathInterval );
// track the lifetime of this new path
float lifetime = GetLifetime();
if ( lifetime > 0.0f )
{
m_lifetimeTimer.Start( lifetime );
}
else
{
m_lifetimeTimer.Invalidate();
}
}
else
{
// can't reach subject - throttle retry based on range to subject
m_failTimer.Start( 0.005f * ( bot->GetRangeTo( subject ) ) );
// allow bot to react to path failure
bot->OnMoveToFailure( this, FAIL_NO_PATH_EXISTS );
if ( bot->IsDebugging( NEXTBOT_PATH ) )
{
const float size = 20.0f;
const float dT = 90.0f;
int c = RandomInt( 0, 100 );
NDebugOverlay::VertArrow( bot->GetPosition() + Vector( 0, 0, size ), bot->GetPosition(), size, 255, c, c, 255, true, dT );
NDebugOverlay::HorzArrow( bot->GetPosition(), pathTarget, 5.0f, 255, c, c, 255, true, dT );
DevMsg( "%3.2f: bot(#%d) REPATH FAILED\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
Invalidate();
}
}
}
//----------------------------------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------------------------------
/**
* Directly beeline toward victim if we have a clear shot, otherwise pathfind.
*/
class DirectChasePath : public ChasePath
{
public:
DirectChasePath( ChasePath::SubjectChaseType chaseHow = ChasePath::DONT_LEAD_SUBJECT ) : ChasePath( chaseHow )
{
}
//-------------------------------------------------------------------------------------------------------
virtual void Update( INextBot *me, CBaseEntity *victim, const IPathCost &pathCost, Vector *pPredictedSubjectPos = NULL ) // update path to chase target and move bot along path
{
Assert( !pPredictedSubjectPos );
bool bComputedPredictedPosition;
Vector vecPredictedPosition;
if ( !DirectChase( &bComputedPredictedPosition, &vecPredictedPosition, me, victim ) )
{
// path around obstacles to reach our victim
ChasePath::Update( me, victim, pathCost, bComputedPredictedPosition ? &vecPredictedPosition : NULL );
}
NotifyVictim( me, victim );
}
//-------------------------------------------------------------------------------------------------------
bool DirectChase( bool *pPredictedPositionComputed, Vector *pPredictedPos, INextBot *me, CBaseEntity *victim ) // if there is nothing between us and our victim, run directly at them
{
*pPredictedPositionComputed = false;
ILocomotion *mover = me->GetLocomotionInterface();
if ( me->IsImmobile() || mover->IsScrambling() )
{
return false;
}
if ( IsDiscontinuityAhead( me, CLIMB_UP ) )
{
return false;
}
if ( IsDiscontinuityAhead( me, JUMP_OVER_GAP ) )
{
return false;
}
Vector leadVictimPos = PredictSubjectPosition( me, victim );
// Don't want to have to compute the predicted position twice.
*pPredictedPositionComputed = true;
*pPredictedPos = leadVictimPos;
if ( !mover->IsPotentiallyTraversable( mover->GetFeet(), leadVictimPos ) )
{
return false;
}
// the way is clear - move directly towards our victim
mover->FaceTowards( leadVictimPos );
mover->Approach( leadVictimPos );
me->GetBodyInterface()->AimHeadTowards( victim );
// old path is no longer useful since we've moved off of it
Invalidate();
return true;
}
//-------------------------------------------------------------------------------------------------------
virtual bool IsRepathNeeded( INextBot *bot, CBaseEntity *subject ) const // return true if situation has changed enough to warrant recomputing the current path
{
if ( ChasePath::IsRepathNeeded( bot, subject ) )
{
return true;
}
return bot->GetLocomotionInterface()->IsStuck() && bot->GetLocomotionInterface()->GetStuckDuration() > 2.0f;
}
//-------------------------------------------------------------------------------------------------------
/**
* Determine exactly where the path goes between the given two areas
* on the path. Return this point in 'crossPos'.
*/
virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const
{
Vector center;
float halfWidth;
from->ComputePortal( to, dir, &center, &halfWidth );
*crossPos = center;
}
void NotifyVictim( INextBot *me, CBaseEntity *victim );
};
#endif // _NEXT_BOT_CHASE_PATH_
File diff suppressed because it is too large Load Diff
+870
View File
@@ -0,0 +1,870 @@
// NextBotPath.h
// Encapsulate and manipulate a path through the world
// Author: Michael Booth, February 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_PATH_H_
#define _NEXT_BOT_PATH_H_
#include "NextBotInterface.h"
#include "tier0/vprof.h"
#define PATH_NO_LENGTH_LIMIT 0.0f // non-default argument value for Path::Compute()
#define PATH_TRUNCATE_INCOMPLETE_PATH false // non-default argument value for Path::Compute()
class INextBot;
class CNavArea;
class CNavLadder;
//---------------------------------------------------------------------------------------------------------------
/**
* The interface for pathfinding costs.
* TODO: Replace all template cost functors with this interface, so we can virtualize and derive from them.
*/
class IPathCost
{
public:
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const = 0;
};
//---------------------------------------------------------------------------------------------------------------
/**
* The interface for selecting a goal area during "open goal" pathfinding
*/
class IPathOpenGoalSelector
{
public:
// compare "newArea" to "currentGoal" and return the area that is the better goal area
virtual CNavArea *operator() ( CNavArea *currentGoal, CNavArea *newArea ) const = 0;
};
//---------------------------------------------------------------------------------------------------------------
/**
* A Path through the world.
* Not only does this encapsulate a path to get from point A to point B,
* but also the selecting the decision algorithm for how to build that path.
*/
class Path
{
public:
Path( void );
virtual ~Path() { }
enum SegmentType
{
ON_GROUND,
DROP_DOWN,
CLIMB_UP,
JUMP_OVER_GAP,
LADDER_UP,
LADDER_DOWN,
NUM_SEGMENT_TYPES
};
// @todo Allow custom Segment classes for different kinds of paths
struct Segment
{
CNavArea *area; // the area along the path
NavTraverseType how; // how to enter this area from the previous one
Vector pos; // our movement goal position at this point in the path
const CNavLadder *ladder; // if "how" refers to a ladder, this is it
SegmentType type; // how to traverse this segment of the path
Vector forward; // unit vector along segment
float length; // length of this segment
float distanceFromStart; // distance of this node from the start of the path
float curvature; // how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
Vector m_portalCenter; // position of center of 'portal' between previous area and this area
float m_portalHalfWidth; // half width of 'portal'
};
virtual float GetLength( void ) const; // return length of path from start to finish
virtual const Vector &GetPosition( float distanceFromStart, const Segment *start = NULL ) const; // return a position on the path at the given distance from the path start
virtual const Vector &GetClosestPosition( const Vector &pos, const Segment *start = NULL, float alongLimit = 0.0f ) const; // return the closest point on the path to the given position
virtual const Vector &GetStartPosition( void ) const; // return the position where this path starts
virtual const Vector &GetEndPosition( void ) const; // return the position where this path ends
virtual CBaseCombatCharacter *GetSubject( void ) const; // return the actor this path leads to, or NULL if there is no subject
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
virtual float GetAge( void ) const; // return "age" of this path (time since it was built)
enum SeekType
{
SEEK_ENTIRE_PATH, // search the entire path length
SEEK_AHEAD, // search from current cursor position forward toward end of path
SEEK_BEHIND // search from current cursor position backward toward path start
};
virtual void MoveCursorToClosestPosition( const Vector &pos, SeekType type = SEEK_ENTIRE_PATH, float alongLimit = 0.0f ) const; // Set cursor position to closest point on path to given position
enum MoveCursorType
{
PATH_ABSOLUTE_DISTANCE,
PATH_RELATIVE_DISTANCE
};
virtual void MoveCursorToStart( void ); // set seek cursor to start of path
virtual void MoveCursorToEnd( void ); // set seek cursor to end of path
virtual void MoveCursor( float value, MoveCursorType type = PATH_ABSOLUTE_DISTANCE ); // change seek cursor position
virtual float GetCursorPosition( void ) const; // return position of seek cursor (distance along path)
struct Data
{
Vector pos; // the position along the path
Vector forward; // unit vector along path direction
float curvature; // how much the path 'curves' at this point in the XY plane (0 = none, 1 = 180 degree doubleback)
const Segment *segmentPrior; // the segment just before this position
};
virtual const Data &GetCursorData( void ) const; // return path state at the current cursor position
virtual bool IsValid( void ) const;
virtual void Invalidate( void ); // make path invalid (clear it)
virtual void Draw( const Path::Segment *start = NULL ) const; // draw the path for debugging
virtual void DrawInterpolated( float from, float to ); // draw the path for debugging - MODIFIES cursor position
virtual const Segment *FirstSegment( void ) const; // return first segment of path
virtual const Segment *NextSegment( const Segment *currentSegment ) const; // return next segment of path, given current one
virtual const Segment *PriorSegment( const Segment *currentSegment ) const; // return previous segment of path, given current one
virtual const Segment *LastSegment( void ) const; // return last segment of path
enum ResultType
{
COMPLETE_PATH,
PARTIAL_PATH,
NO_PATH
};
virtual void OnPathChanged( INextBot *bot, ResultType result ) { } // invoked when the path is (re)computed (path is valid at the time of this call)
virtual void Copy( INextBot *bot, const Path &path ); // Replace this path with the given path's data
//-----------------------------------------------------------------------------------------------------------------
/**
* Compute shortest path from bot to given actor via A* algorithm.
* If returns true, path was found to the subject.
* If returns false, path may either be invalid (use IsValid() to check), or valid but
* doesn't reach all the way to the subject.
*/
template< typename CostFunctor >
bool Compute( INextBot *bot, CBaseCombatCharacter *subject, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
{
VPROF_BUDGET( "Path::Compute(subject)", "NextBot" );
Invalidate();
m_subject = subject;
const Vector &start = bot->GetPosition();
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
if ( !startArea )
{
OnPathChanged( bot, NO_PATH );
return false;
}
CNavArea *subjectArea = subject->GetLastKnownArea();
if ( !subjectArea )
{
OnPathChanged( bot, NO_PATH );
return false;
}
Vector subjectPos = subject->GetAbsOrigin();
// if we are already in the subject area, build trivial path
if ( startArea == subjectArea )
{
BuildTrivialPath( bot, subjectPos );
return true;
}
//
// Compute shortest path to subject
//
CNavArea *closestArea = NULL;
bool pathResult = NavAreaBuildPath( startArea, subjectArea, &subjectPos, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );
//
// Build actual path by following parent links back from subject area
//
// get count
int count = 0;
CNavArea *area;
for( area = closestArea; area; area = area->GetParent() )
{
++count;
if ( area == startArea )
{
// startArea can be re-evaluated during the pathfind and given a parent...
break;
}
}
// save room for endpoint
if ( count > MAX_PATH_SEGMENTS-1 )
{
count = MAX_PATH_SEGMENTS-1;
}
else if ( count == 0 )
{
return false;
}
if ( count == 1 )
{
BuildTrivialPath( bot, subjectPos );
return pathResult;
}
// assemble path
m_segmentCount = count;
for( area = closestArea; count && area; area = area->GetParent() )
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
m_path[ count ].type = ON_GROUND;
}
if ( pathResult || includeGoalIfPathFails )
{
// append actual subject position
m_path[ m_segmentCount ].area = closestArea;
m_path[ m_segmentCount ].pos = subjectPos;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
m_path[ m_segmentCount ].type = ON_GROUND;
++m_segmentCount;
}
// compute path positions
if ( ComputePathDetails( bot, start ) == false )
{
Invalidate();
OnPathChanged( bot, NO_PATH );
return false;
}
// remove redundant nodes and clean up path
Optimize( bot );
PostProcess();
OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );
return pathResult;
}
//-----------------------------------------------------------------------------------------------------------------
/**
* Compute shortest path from bot to 'goal' via A* algorithm.
* If returns true, path was found to the goal position.
* If returns false, path may either be invalid (use IsValid() to check), or valid but
* doesn't reach all the way to the goal.
*/
template< typename CostFunctor >
bool Compute( INextBot *bot, const Vector &goal, CostFunctor &costFunc, float maxPathLength = 0.0f, bool includeGoalIfPathFails = true )
{
VPROF_BUDGET( "Path::Compute(goal)", "NextBotSpiky" );
Invalidate();
const Vector &start = bot->GetPosition();
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
if ( !startArea )
{
OnPathChanged( bot, NO_PATH );
return false;
}
// check line-of-sight to the goal position when finding it's nav area
const float maxDistanceToArea = 200.0f;
CNavArea *goalArea = TheNavMesh->GetNearestNavArea( goal, true, maxDistanceToArea, true );
// if we are already in the goal area, build trivial path
if ( startArea == goalArea )
{
BuildTrivialPath( bot, goal );
return true;
}
// make sure path end position is on the ground
Vector pathEndPosition = goal;
if ( goalArea )
{
pathEndPosition.z = goalArea->GetZ( pathEndPosition );
}
else
{
TheNavMesh->GetGroundHeight( pathEndPosition, &pathEndPosition.z );
}
//
// Compute shortest path to goal
//
CNavArea *closestArea = NULL;
bool pathResult = NavAreaBuildPath( startArea, goalArea, &goal, costFunc, &closestArea, maxPathLength, bot->GetEntity()->GetTeamNumber() );
//
// Build actual path by following parent links back from goal area
//
// get count
int count = 0;
CNavArea *area;
for( area = closestArea; area; area = area->GetParent() )
{
++count;
if ( area == startArea )
{
// startArea can be re-evaluated during the pathfind and given a parent...
break;
}
}
// save room for endpoint
if ( count > MAX_PATH_SEGMENTS-1 )
{
count = MAX_PATH_SEGMENTS-1;
}
else if ( count == 0 )
{
return false;
}
if ( count == 1 )
{
BuildTrivialPath( bot, goal );
return pathResult;
}
// assemble path
m_segmentCount = count;
for( area = closestArea; count && area; area = area->GetParent() )
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
m_path[ count ].type = ON_GROUND;
}
if ( pathResult || includeGoalIfPathFails )
{
// append actual goal position
m_path[ m_segmentCount ].area = closestArea;
m_path[ m_segmentCount ].pos = pathEndPosition;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
m_path[ m_segmentCount ].type = ON_GROUND;
++m_segmentCount;
}
// compute path positions
if ( ComputePathDetails( bot, start ) == false )
{
Invalidate();
OnPathChanged( bot, NO_PATH );
return false;
}
// remove redundant nodes and clean up path
Optimize( bot );
PostProcess();
OnPathChanged( bot, pathResult ? COMPLETE_PATH : PARTIAL_PATH );
return pathResult;
}
//-----------------------------------------------------------------------------------------------------------------
/**
* Build a path from bot's current location to an undetermined goal area
* that minimizes the given cost along the final path and meets the
* goal criteria.
*/
virtual bool ComputeWithOpenGoal( INextBot *bot, const IPathCost &costFunc, const IPathOpenGoalSelector &goalSelector, float maxSearchRadius = 0.0f )
{
VPROF_BUDGET( "ComputeWithOpenGoal", "NextBot" );
int teamID = bot->GetEntity()->GetTeamNumber();
CNavArea *startArea = bot->GetEntity()->GetLastKnownArea();
if ( startArea == NULL )
return NULL;
startArea->SetParent( NULL );
// start search
CNavArea::ClearSearchLists();
float initCost = costFunc( startArea, NULL, NULL, NULL, -1.0f );
if ( initCost < 0.0f )
return NULL;
startArea->SetTotalCost( initCost );
startArea->AddToOpenList();
// find our goal as we search
CNavArea *goalArea = NULL;
//
// Dijkstra's algorithm (since we don't know our goal).
//
while( !CNavArea::IsOpenListEmpty() )
{
// get next area to check
CNavArea *area = CNavArea::PopOpenList();
area->AddToClosedList();
// don't consider blocked areas
if ( area->IsBlocked( teamID ) )
continue;
// build adjacent area array
CollectAdjacentAreas( area );
// search adjacent areas
for( int i=0; i<m_adjAreaIndex; ++i )
{
CNavArea *newArea = m_adjAreaVector[ i ].area;
// only visit each area once
if ( newArea->IsClosed() )
continue;
// don't consider blocked areas
if ( newArea->IsBlocked( teamID ) )
continue;
// don't use this area if it is out of range
if ( maxSearchRadius > 0.0f && ( newArea->GetCenter() - bot->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( maxSearchRadius ) )
continue;
// determine cost of traversing this area
float newCost = costFunc( newArea, area, m_adjAreaVector[ i ].ladder, NULL, -1.0f );
// don't use adjacent area if cost functor says it is a dead-end
if ( newCost < 0.0f )
continue;
if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
{
// we have already visited this area, and it has a better path
continue;
}
else
{
// whether this area has been visited or not, we now have a better path to it
newArea->SetParent( area, m_adjAreaVector[ i ].how );
newArea->SetTotalCost( newCost );
// use 'cost so far' to hold cumulative cost
newArea->SetCostSoFar( newCost );
// tricky bit here - relying on OpenList being sorted by cost
if ( newArea->IsOpen() )
{
// area already on open list, update the list order to keep costs sorted
newArea->UpdateOnOpenList();
}
else
{
newArea->AddToOpenList();
}
// keep track of best goal so far
goalArea = goalSelector( goalArea, newArea );
}
}
}
if ( goalArea )
{
// compile the path details into a usable path
AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
return true;
}
// all adjacent areas are likely too far away
return false;
}
//-----------------------------------------------------------------------------------------------------------------
/**
* Given the last area in a path with valid parent pointers,
* construct the actual path.
*/
void AssemblePrecomputedPath( INextBot *bot, const Vector &goal, CNavArea *endArea )
{
VPROF_BUDGET( "AssemblePrecomputedPath", "NextBot" );
const Vector &start = bot->GetPosition();
// get count
int count = 0;
CNavArea *area;
for( area = endArea; area; area = area->GetParent() )
{
++count;
}
// save room for endpoint
if ( count > MAX_PATH_SEGMENTS-1 )
{
count = MAX_PATH_SEGMENTS-1;
}
else if ( count == 0 )
{
return;
}
if ( count == 1 )
{
BuildTrivialPath( bot, goal );
return;
}
// assemble path
m_segmentCount = count;
for( area = endArea; count && area; area = area->GetParent() )
{
--count;
m_path[ count ].area = area;
m_path[ count ].how = area->GetParentHow();
m_path[ count ].type = ON_GROUND;
}
// append actual goal position
m_path[ m_segmentCount ].area = endArea;
m_path[ m_segmentCount ].pos = goal;
m_path[ m_segmentCount ].ladder = NULL;
m_path[ m_segmentCount ].how = NUM_TRAVERSE_TYPES;
m_path[ m_segmentCount ].type = ON_GROUND;
++m_segmentCount;
// compute path positions
if ( ComputePathDetails( bot, start ) == false )
{
Invalidate();
OnPathChanged( bot, NO_PATH );
return;
}
// remove redundant nodes and clean up path
Optimize( bot );
PostProcess();
OnPathChanged( bot, COMPLETE_PATH );
}
/**
* Utility function for when start and goal are in the same area
*/
bool BuildTrivialPath( INextBot *bot, const Vector &goal );
/**
* Determine exactly where the path goes between the given two areas
* on the path. Return this point in 'crossPos'.
*/
virtual void ComputeAreaCrossing( INextBot *bot, const CNavArea *from, const Vector &fromPos, const CNavArea *to, NavDirType dir, Vector *crossPos ) const;
private:
enum { MAX_PATH_SEGMENTS = 256 };
Segment m_path[ MAX_PATH_SEGMENTS ];
int m_segmentCount;
bool ComputePathDetails( INextBot *bot, const Vector &start ); // determine actual path positions
void Optimize( INextBot *bot );
void PostProcess( void );
int FindNextOccludedNode( INextBot *bot, int anchor ); // used by Optimize()
void InsertSegment( Segment newSegment, int i ); // insert new segment at index i
mutable Vector m_pathPos; // used by GetPosition()
mutable Vector m_closePos; // used by GetClosestPosition()
mutable float m_cursorPos; // current cursor position (distance along path)
mutable Data m_cursorData; // used by GetCursorData()
mutable bool m_isCursorDataDirty;
IntervalTimer m_ageTimer; // how old is this path?
CHandle< CBaseCombatCharacter > m_subject; // the subject this path leads to
/**
* Build a vector of adjacent areas reachable from the given area
*/
void CollectAdjacentAreas( CNavArea *area )
{
m_adjAreaIndex = 0;
const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
FOR_EACH_VEC( adjNorth, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
FOR_EACH_VEC( adjSouth, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
FOR_EACH_VEC( adjWest, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
FOR_EACH_VEC( adjEast, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
FOR_EACH_VEC( adjUpLadder, it )
{
CNavLadder *ladder = adjUpLadder[ it ].ladder;
if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
}
const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
FOR_EACH_VEC( adjDownLadder, it )
{
CNavLadder *ladder = adjDownLadder[ it ].ladder;
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
if ( ladder->m_bottomArea )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
}
}
enum { MAX_ADJ_AREAS = 64 };
struct AdjInfo
{
CNavArea *area;
CNavLadder *ladder;
NavTraverseType how;
};
AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
int m_adjAreaIndex;
};
inline float Path::GetLength( void ) const
{
if (m_segmentCount <= 0)
{
return 0.0f;
}
return m_path[ m_segmentCount-1 ].distanceFromStart;
}
inline bool Path::IsValid( void ) const
{
return (m_segmentCount > 0);
}
inline void Path::Invalidate( void )
{
m_segmentCount = 0;
m_cursorPos = 0.0f;
m_cursorData.pos = vec3_origin;
m_cursorData.forward = Vector( 1.0f, 0, 0 );
m_cursorData.curvature = 0.0f;
m_cursorData.segmentPrior = NULL;
m_isCursorDataDirty = true;
m_subject = NULL;
}
inline const Path::Segment *Path::FirstSegment( void ) const
{
return (IsValid()) ? &m_path[0] : NULL;
}
inline const Path::Segment *Path::NextSegment( const Segment *currentSegment ) const
{
if (currentSegment == NULL || !IsValid())
return NULL;
int i = currentSegment - m_path;
if (i < 0 || i >= m_segmentCount-1)
{
return NULL;
}
return &m_path[ i+1 ];
}
inline const Path::Segment *Path::PriorSegment( const Segment *currentSegment ) const
{
if (currentSegment == NULL || !IsValid())
return NULL;
int i = currentSegment - m_path;
if (i < 1 || i >= m_segmentCount)
{
return NULL;
}
return &m_path[ i-1 ];
}
inline const Path::Segment *Path::LastSegment( void ) const
{
return ( IsValid() ) ? &m_path[ m_segmentCount-1 ] : NULL;
}
inline const Vector &Path::GetStartPosition( void ) const
{
return ( IsValid() ) ? m_path[ 0 ].pos : vec3_origin;
}
inline const Vector &Path::GetEndPosition( void ) const
{
return ( IsValid() ) ? m_path[ m_segmentCount-1 ].pos : vec3_origin;
}
inline CBaseCombatCharacter *Path::GetSubject( void ) const
{
return m_subject;
}
inline void Path::MoveCursorToStart( void )
{
m_cursorPos = 0.0f;
m_isCursorDataDirty = true;
}
inline void Path::MoveCursorToEnd( void )
{
m_cursorPos = GetLength();
m_isCursorDataDirty = true;
}
inline void Path::MoveCursor( float value, MoveCursorType type )
{
if ( type == PATH_ABSOLUTE_DISTANCE )
{
m_cursorPos = value;
}
else // relative distance
{
m_cursorPos += value;
}
if ( m_cursorPos < 0.0f )
{
m_cursorPos = 0.0f;
}
else if ( m_cursorPos > GetLength() )
{
m_cursorPos = GetLength();
}
m_isCursorDataDirty = true;
}
inline float Path::GetCursorPosition( void ) const
{
return m_cursorPos;
}
inline const Path::Segment *Path::GetCurrentGoal( void ) const
{
return NULL;
}
inline float Path::GetAge( void ) const
{
return m_ageTimer.GetElapsedTime();
}
#endif // _NEXT_BOT_PATH_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,96 @@
// NextBotPathFollow.h
// Path following
// Author: Michael Booth, April 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_PATH_FOLLOWER_
#define _NEXT_BOT_PATH_FOLLOWER_
#include "nav_mesh.h"
#include "nav_pathfind.h"
#include "NextBotPath.h"
class INextBot;
class ILocomotion;
//--------------------------------------------------------------------------------------------------------
/**
* A PathFollower extends a Path to include mechanisms to move along (follow) it
*/
class PathFollower : public Path
{
public:
PathFollower( void );
virtual ~PathFollower();
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
virtual void Draw( const Path::Segment *start = NULL ) const; // (EXTEND) draw the path for debugging
virtual void OnPathChanged( INextBot *bot, Path::ResultType result ); // invoked when the path is (re)computed (path is valid at the time of this call)
virtual void Update( INextBot *bot ); // move bot along path
virtual const Path::Segment *GetCurrentGoal( void ) const; // return current goal along the path we are trying to reach
virtual void SetMinLookAheadDistance( float value ); // minimum range movement goal must be along path
virtual CBaseEntity *GetHindrance( void ) const; // returns entity that is hindering our progress along the path
virtual bool IsDiscontinuityAhead( INextBot *bot, Path::SegmentType type, float range = -1.0f ) const; // return true if there is a the given discontinuity ahead in the path within the given range (-1 = entire remaining path)
private:
const Path::Segment *m_goal; // our current goal along the path
float m_minLookAheadRange;
bool CheckProgress( INextBot *bot );
bool IsAtGoal( INextBot *bot ) const; // return true if reached current path goal
//bool IsOnStairs( INextBot *bot ) const; // return true if bot is standing on a stairway
bool m_isOnStairs;
CountdownTimer m_avoidTimer; // do avoid check more often if we recently avoided
CountdownTimer m_waitTimer; // for waiting for a blocker to move off our path
CHandle< CBaseEntity > m_hindrance;
// debug display data for avoid volumes
bool m_didAvoidCheck;
Vector m_leftFrom;
Vector m_leftTo;
bool m_isLeftClear;
Vector m_rightFrom;
Vector m_rightTo;
bool m_isRightClear;
Vector m_hullMin, m_hullMax;
void AdjustSpeed( INextBot *bot ); // adjust speed based on path curvature
Vector Avoid( INextBot *bot, const Vector &goalPos, const Vector &forward, const Vector &left ); // avoidance movements for very nearby obstacles. returns modified goal position
bool Climbing( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &left, float goalRange ); // climb up ledges
bool JumpOverGaps( INextBot *bot, const Path::Segment *goal, const Vector &forward, const Vector &left, float goalRange ); // jump over gaps
bool LadderUpdate( INextBot *bot ); // move bot along ladder
CBaseEntity *FindBlocker( INextBot *bot ); // if entity is returned, it is blocking us from continuing along our path
};
inline const Path::Segment *PathFollower::GetCurrentGoal( void ) const
{
return m_goal;
}
inline void PathFollower::SetMinLookAheadDistance( float value )
{
m_minLookAheadRange = value;
}
inline CBaseEntity *PathFollower::GetHindrance( void ) const
{
return m_hindrance;
}
#endif // _NEXT_BOT_PATH_FOLLOWER_
@@ -0,0 +1,573 @@
// NextBotRetreatPath.h
// Maintain and follow a path that leads safely away from the given Actor
// Author: Michael Booth, February 2007
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_RETREAT_PATH_
#define _NEXT_BOT_RETREAT_PATH_
#include "nav.h"
#include "NextBotInterface.h"
#include "NextBotLocomotionInterface.h"
#include "NextBotRetreatPath.h"
#include "NextBotUtil.h"
#include "NextBotPathFollow.h"
#include "tier0/vprof.h"
//----------------------------------------------------------------------------------------------
/**
* A RetreatPath extends a PathFollower to periodically recompute a path
* away from a threat, and to move along the path away from that threat.
*/
class RetreatPath : public PathFollower
{
public:
RetreatPath( void );
virtual ~RetreatPath() { }
void Update( INextBot *bot, CBaseEntity *threat ); // update path away from threat and move bot along path
virtual float GetMaxPathLength( void ) const; // return maximum path length
virtual void Invalidate( void ); // (EXTEND) cause the path to become invalid
private:
void RefreshPath( INextBot *bot, CBaseEntity *threat );
CountdownTimer m_throttleTimer; // require a minimum time between re-paths
EHANDLE m_pathThreat; // the threat of our existing path
Vector m_pathThreatPos; // where the threat was when the path was built
};
inline RetreatPath::RetreatPath( void )
{
m_throttleTimer.Invalidate();
m_pathThreat = NULL;
}
inline float RetreatPath::GetMaxPathLength( void ) const
{
return 1000.0f;
}
inline void RetreatPath::Invalidate( void )
{
// path is gone, repath at earliest opportunity
m_throttleTimer.Invalidate();
m_pathThreat = NULL;
// extend
PathFollower::Invalidate();
}
//----------------------------------------------------------------------------------------------
/**
* Maintain a path to our chase threat and move along that path
*/
inline void RetreatPath::Update( INextBot *bot, CBaseEntity *threat )
{
VPROF_BUDGET( "RetreatPath::Update", "NextBot" );
if ( threat == NULL )
{
return;
}
// if our path threat changed, repath immediately
if ( threat != m_pathThreat )
{
if ( bot->IsDebugging( INextBot::PATH ) )
{
DevMsg( "%3.2f: bot(#%d) Chase path threat changed (from %X to %X).\n", gpGlobals->curtime, bot->GetEntity()->entindex(), m_pathThreat.Get(), threat );
}
Invalidate();
}
// maintain the path away from the threat
RefreshPath( bot, threat );
// move along the path towards the threat
PathFollower::Update( bot );
}
//--------------------------------------------------------------------------------------------------------------
/**
* Build a path away from retreatFromArea up to retreatRange in length.
*/
class RetreatPathBuilder
{
public:
RetreatPathBuilder( INextBot *me, CBaseEntity *threat, float retreatRange = 500.0f )
{
m_me = me;
m_mover = me->GetLocomotionInterface();
m_threat = threat;
m_retreatRange = retreatRange;
}
CNavArea *ComputePath( void )
{
VPROF_BUDGET( "NavAreaBuildRetreatPath", "NextBot" );
if ( m_mover == NULL )
return NULL;
CNavArea *startArea = m_me->GetEntity()->GetLastKnownArea();
if ( startArea == NULL )
return NULL;
CNavArea *retreatFromArea = TheNavMesh->GetNearestNavArea( m_threat->GetAbsOrigin() );
if ( retreatFromArea == NULL )
return NULL;
startArea->SetParent( NULL );
// start search
CNavArea::ClearSearchLists();
float initCost = Cost( startArea, NULL, NULL );
if ( initCost < 0.0f )
return NULL;
int teamID = m_me->GetEntity()->GetTeamNumber();
startArea->SetTotalCost( initCost );
startArea->AddToOpenList();
// keep track of the area farthest away from the threat
CNavArea *farthestArea = NULL;
float farthestRange = 0.0f;
//
// Dijkstra's algorithm (since we don't know our goal).
// Build a path as far away from the retreat area as possible.
// Minimize total path length and danger.
// Maximize distance to threat of end of path.
//
while( !CNavArea::IsOpenListEmpty() )
{
// get next area to check
CNavArea *area = CNavArea::PopOpenList();
area->AddToClosedList();
// don't consider blocked areas
if ( area->IsBlocked( teamID ) )
continue;
// build adjacent area array
CollectAdjacentAreas( area );
// search adjacent areas
for( int i=0; i<m_adjAreaIndex; ++i )
{
CNavArea *newArea = m_adjAreaVector[ i ].area;
// only visit each area once
if ( newArea->IsClosed() )
continue;
// don't consider blocked areas
if ( newArea->IsBlocked( teamID ) )
continue;
// don't use this area if it is out of range
if ( ( newArea->GetCenter() - m_me->GetEntity()->GetAbsOrigin() ).IsLengthGreaterThan( m_retreatRange ) )
continue;
// determine cost of traversing this area
float newCost = Cost( newArea, area, m_adjAreaVector[ i ].ladder );
// don't use adjacent area if cost functor says it is a dead-end
if ( newCost < 0.0f )
continue;
if ( newArea->IsOpen() && newArea->GetTotalCost() <= newCost )
{
// we have already visited this area, and it has a better path
continue;
}
else
{
// whether this area has been visited or not, we now have a better path
newArea->SetParent( area, m_adjAreaVector[ i ].how );
newArea->SetTotalCost( newCost );
// use 'cost so far' to hold cumulative cost
newArea->SetCostSoFar( newCost );
// tricky bit here - relying on OpenList being sorted by cost
if ( newArea->IsOpen() )
{
// area already on open list, update the list order to keep costs sorted
newArea->UpdateOnOpenList();
}
else
{
newArea->AddToOpenList();
}
// keep track of area farthest from threat
float threatRange = ( newArea->GetCenter() - m_threat->GetAbsOrigin() ).Length();
if ( threatRange > farthestRange )
{
farthestArea = newArea;
farthestRange = threatRange;
}
}
}
}
return farthestArea;
}
/**
* Build a vector of adjacent areas reachable from the given area
*/
void CollectAdjacentAreas( CNavArea *area )
{
m_adjAreaIndex = 0;
const NavConnectVector &adjNorth = *area->GetAdjacentAreas( NORTH );
FOR_EACH_VEC( adjNorth, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjNorth[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_NORTH;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjSouth = *area->GetAdjacentAreas( SOUTH );
FOR_EACH_VEC( adjSouth, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjSouth[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_SOUTH;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjWest = *area->GetAdjacentAreas( WEST );
FOR_EACH_VEC( adjWest, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjWest[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_WEST;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavConnectVector &adjEast = *area->GetAdjacentAreas( EAST );
FOR_EACH_VEC( adjEast, it )
{
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
m_adjAreaVector[ m_adjAreaIndex ].area = adjEast[ it ].area;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_EAST;
m_adjAreaVector[ m_adjAreaIndex ].ladder = NULL;
++m_adjAreaIndex;
}
const NavLadderConnectVector &adjUpLadder = *area->GetLadders( CNavLadder::LADDER_UP );
FOR_EACH_VEC( adjUpLadder, it )
{
CNavLadder *ladder = adjUpLadder[ it ].ladder;
if ( ladder->m_topForwardArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topForwardArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
if ( ladder->m_topLeftArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topLeftArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
if ( ladder->m_topRightArea && m_adjAreaIndex < MAX_ADJ_AREAS )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_topRightArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_UP;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
}
const NavLadderConnectVector &adjDownLadder = *area->GetLadders( CNavLadder::LADDER_DOWN );
FOR_EACH_VEC( adjDownLadder, it )
{
CNavLadder *ladder = adjDownLadder[ it ].ladder;
if ( m_adjAreaIndex >= MAX_ADJ_AREAS )
break;
if ( ladder->m_bottomArea )
{
m_adjAreaVector[ m_adjAreaIndex ].area = ladder->m_bottomArea;
m_adjAreaVector[ m_adjAreaIndex ].how = GO_LADDER_DOWN;
m_adjAreaVector[ m_adjAreaIndex ].ladder = ladder;
++m_adjAreaIndex;
}
}
}
/**
* Cost minimizes path length traveled thus far and "danger" (proximity to threat(s))
*/
float Cost( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder )
{
// check if we can use this area
if ( !m_mover->IsAreaTraversable( area ) )
{
return -1.0f;
}
int teamID = m_me->GetEntity()->GetTeamNumber();
if ( area->IsBlocked( teamID ) )
{
return -1.0f;
}
const float debugDeltaT = 3.0f;
float cost;
const float maxThreatRange = 500.0f;
const float dangerDensity = 1000.0f;
if ( fromArea == NULL )
{
cost = 0.0f;
if ( area->Contains( m_threat->GetAbsOrigin() ) )
{
// maximum danger - threat is in the area with us
cost += 10.0f * dangerDensity;
if ( m_me->IsDebugging( INextBot::PATH ) )
{
area->DrawFilled( 255, 0, 0, 128 );
}
}
else
{
// danger proportional to range to us
float rangeToThreat = ( m_threat->GetAbsOrigin() - m_me->GetEntity()->GetAbsOrigin() ).Length();
if ( rangeToThreat < maxThreatRange )
{
cost += dangerDensity * ( 1.0f - ( rangeToThreat / maxThreatRange ) );
if ( m_me->IsDebugging( INextBot::PATH ) )
{
NDebugOverlay::Line( m_me->GetEntity()->GetAbsOrigin(), m_threat->GetAbsOrigin(), 255, 0, 0, true, debugDeltaT );
}
}
}
}
else
{
// compute distance traveled along path so far
float dist;
if ( ladder )
{
const float ladderCostFactor = 100.0f;
dist = ladderCostFactor * ladder->m_length;
}
else
{
Vector to = area->GetCenter() - fromArea->GetCenter();
dist = to.Length();
// check for vertical discontinuities
Vector closeFrom, closeTo;
area->GetClosestPointOnArea( fromArea->GetCenter(), &closeTo );
fromArea->GetClosestPointOnArea( area->GetCenter(), &closeFrom );
float deltaZ = closeTo.z - closeFrom.z;
if ( deltaZ > m_mover->GetMaxJumpHeight() )
{
// too high to jump
return -1.0f;
}
else if ( -deltaZ > m_mover->GetDeathDropHeight() )
{
// too far down to drop
return -1.0f;
}
// prefer to maintain our level
const float climbCost = 10.0f;
dist += climbCost * fabs( deltaZ );
}
cost = dist + fromArea->GetTotalCost();
// Add in danger cost due to threat
// Assume straight line between areas and find closest point
// to the threat along that line segment. The distance between
// the threat and closest point on the line is the danger cost.
// path danger is CUMULATIVE
float dangerCost = fromArea->GetCostSoFar();
Vector close;
float t;
CalcClosestPointOnLineSegment( m_threat->GetAbsOrigin(), area->GetCenter(), fromArea->GetCenter(), close, &t );
if ( t < 0.0f )
{
close = area->GetCenter();
}
else if ( t > 1.0f )
{
close = fromArea->GetCenter();
}
float rangeToThreat = ( m_threat->GetAbsOrigin() - close ).Length();
if ( rangeToThreat < maxThreatRange )
{
float dangerFactor = 1.0f - ( rangeToThreat / maxThreatRange );
dangerCost = dangerDensity * dangerFactor;
if ( m_me->IsDebugging( INextBot::PATH ) )
{
NDebugOverlay::HorzArrow( fromArea->GetCenter(), area->GetCenter(), 5, 255 * dangerFactor, 0, 0, 255, true, debugDeltaT );
Vector to = close - m_threat->GetAbsOrigin();
to.NormalizeInPlace();
NDebugOverlay::Line( close, close - 50.0f * to, 255, 0, 0, true, debugDeltaT );
}
}
cost += dangerCost;
}
return cost;
}
private:
INextBot *m_me;
ILocomotion *m_mover;
CBaseEntity *m_threat;
float m_retreatRange;
enum { MAX_ADJ_AREAS = 64 };
struct AdjInfo
{
CNavArea *area;
CNavLadder *ladder;
NavTraverseType how;
};
AdjInfo m_adjAreaVector[ MAX_ADJ_AREAS ];
int m_adjAreaIndex;
};
//----------------------------------------------------------------------------------------------
/**
* Periodically rebuild the path away from our threat
*/
inline void RetreatPath::RefreshPath( INextBot *bot, CBaseEntity *threat )
{
VPROF_BUDGET( "RetreatPath::RefreshPath", "NextBot" );
if ( threat == NULL )
{
if ( bot->IsDebugging( INextBot::PATH ) )
{
DevMsg( "%3.2f: bot(#%d) CasePath::RefreshPath failed. No threat.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
return;
}
// don't change our path if we're on a ladder
ILocomotion *mover = bot->GetLocomotionInterface();
if ( IsValid() && mover && mover->IsUsingLadder() )
{
if ( bot->IsDebugging( INextBot::PATH ) )
{
DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Bot is on a ladder.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
return;
}
// the closer we get, the more accurate our path needs to be
Vector to = threat->GetAbsOrigin() - bot->GetPosition();
const float minTolerance = 0.0f;
const float toleranceRate = 0.33f;
float tolerance = minTolerance + toleranceRate * to.Length();
if ( !IsValid() || ( threat->GetAbsOrigin() - m_pathThreatPos ).IsLengthGreaterThan( tolerance ) )
{
if ( !m_throttleTimer.IsElapsed() )
{
// require a minimum time between repaths, as long as we have a path to follow
if ( bot->IsDebugging( INextBot::PATH ) )
{
DevMsg( "%3.2f: bot(#%d) RetreatPath::RefreshPath failed. Rate throttled.\n", gpGlobals->curtime, bot->GetEntity()->entindex() );
}
return;
}
// remember our path threat
m_pathThreat = threat;
m_pathThreatPos = threat->GetAbsOrigin();
RetreatPathBuilder retreat( bot, threat, GetMaxPathLength() );
CNavArea *goalArea = retreat.ComputePath();
if ( goalArea )
{
AssemblePrecomputedPath( bot, goalArea->GetCenter(), goalArea );
}
else
{
// all adjacent areas are too far away - just move directly away from threat
Vector to = threat->GetAbsOrigin() - bot->GetPosition();
BuildTrivialPath( bot, bot->GetPosition() - to );
}
const float minRepathInterval = 0.5f;
m_throttleTimer.Start( minRepathInterval );
}
}
#endif // _NEXT_BOT_RETREAT_PATH_
@@ -0,0 +1,22 @@
// NextBotPlayer.cpp
// A CBasePlayer bot based on the NextBot technology
// Author: Michael Booth, November 2005
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "nav_mesh.h"
#include "NextBot.h"
#include "NextBotPlayer.h"
#include "in_buttons.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar NextBotPlayerStop( "nb_player_stop", "0", FCVAR_CHEAT, "Stop all NextBotPlayers from updating" );
ConVar NextBotPlayerWalk( "nb_player_walk", "0", FCVAR_CHEAT, "Force bots to walk" );
ConVar NextBotPlayerCrouch( "nb_player_crouch", "0", FCVAR_CHEAT, "Force bots to crouch" );
ConVar NextBotPlayerMove( "nb_player_move", "1", FCVAR_CHEAT, "Prevents bots from moving" );
+862
View File
@@ -0,0 +1,862 @@
// NextBotPlayer.h
// A CBasePlayer bot based on the NextBot technology
// Author: Michael Booth, November 2005
// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_PLAYER_H_
#define _NEXT_BOT_PLAYER_H_
#include "cbase.h"
#include "gameinterface.h"
#include "NextBot.h"
#include "Path/NextBotPathFollow.h"
//#include "NextBotPlayerBody.h"
#include "NextBotBehavior.h"
#include "in_buttons.h"
extern ConVar NextBotPlayerStop;
extern ConVar NextBotPlayerWalk;
extern ConVar NextBotPlayerCrouch;
extern ConVar NextBotPlayerMove;
//--------------------------------------------------------------------------------------------------
/**
* Instantiate a NextBot derived from CBasePlayer and spawn it into the environment.
* Assumes class T is derived from CBasePlayer, and has the following method that
* creates a new entity of type T and returns it:
*
* static CBasePlayer *T::AllocatePlayerEntity( edict_t *pEdict, const char *playerName )
*
*/
template < typename T >
T * NextBotCreatePlayerBot( const char *name )
{
/*
if ( UTIL_ClientsInGame() >= gpGlobals->maxClients )
{
Msg( "CreatePlayerBot: Failed - server is full (%d/%d clients).\n", UTIL_ClientsInGame(), gpGlobals->maxClients );
return NULL;
}
*/
// This is a "back door" for allocating a custom player bot entity when
// the engine calls ClientPutInServer (from CreateFakeClient)
ClientPutInServerOverride( T::AllocatePlayerEntity );
// create the bot and spawn it into the environment
edict_t *botEdict = engine->CreateFakeClient( name );
// close the "back door"
ClientPutInServerOverride( NULL );
if ( botEdict == NULL )
{
Msg( "CreatePlayerBot: Unable to create bot %s - CreateFakeClient() returned NULL.\n", name );
return NULL;
}
// create an instance of the bot's class and bind it to the edict
T *bot = dynamic_cast< T * >( CBaseEntity::Instance( botEdict ) );
if ( bot == NULL )
{
Assert( false );
Error( "CreatePlayerBot: Could not Instance() from the bot edict.\n" );
return NULL;
}
// set the fakeclient's name
// char trimmedName[ MAX_PLAYER_NAME_LENGTH ];
// Q_strncpy( trimmedName, name, sizeof( trimmedName ) );
// bot->PlayerData()->netname = AllocPooledString( trimmedName );
bot->SetPlayerName( name );
// flag this as a fakeclient (bot)
bot->ClearFlags();
bot->AddFlag( FL_CLIENT | FL_FAKECLIENT );
return bot;
}
//--------------------------------------------------------------------------------------------------
/**
* Interface to access player input buttons.
* Unless a duration is given, each button is released at the start of the next frame.
* The release methods allow releasing a button before its duration has elapsed.
*/
class INextBotPlayerInput
{
public:
virtual void PressFireButton( float duration = -1.0f ) = 0;
virtual void ReleaseFireButton( void ) = 0;
virtual void PressAltFireButton( float duration = -1.0f ) = 0;
virtual void ReleaseAltFireButton( void ) = 0;
virtual void PressMeleeButton( float duration = -1.0f ) = 0;
virtual void ReleaseMeleeButton( void ) = 0;
virtual void PressUseButton( float duration = -1.0f ) = 0;
virtual void ReleaseUseButton( void ) = 0;
virtual void PressReloadButton( float duration = -1.0f ) = 0;
virtual void ReleaseReloadButton( void ) = 0;
virtual void PressForwardButton( float duration = -1.0f ) = 0;
virtual void ReleaseForwardButton( void ) = 0;
virtual void PressBackwardButton( float duration = -1.0f ) = 0;
virtual void ReleaseBackwardButton( void ) = 0;
virtual void PressLeftButton( float duration = -1.0f ) = 0;
virtual void ReleaseLeftButton( void ) = 0;
virtual void PressRightButton( float duration = -1.0f ) = 0;
virtual void ReleaseRightButton( void ) = 0;
virtual void PressJumpButton( float duration = -1.0f ) = 0;
virtual void ReleaseJumpButton( void ) = 0;
virtual void PressCrouchButton( float duration = -1.0f ) = 0;
virtual void ReleaseCrouchButton( void ) = 0;
virtual void PressWalkButton( float duration = -1.0f ) = 0;
virtual void ReleaseWalkButton( void ) = 0;
virtual void SetButtonScale( float forward, float right ) = 0;
};
//--------------------------------------------------------------------------------------------------
/**
* Drive a CBasePlayer-derived entity via NextBot logic
*/
template < typename PlayerType >
class NextBotPlayer : public PlayerType, public INextBot, public INextBotPlayerInput
{
public:
DECLARE_CLASS( NextBotPlayer, PlayerType );
NextBotPlayer( void );
virtual ~NextBotPlayer();
virtual void Spawn( void );
virtual void PhysicsSimulate( void );
virtual bool IsNetClient( void ) const { return false; } // Bots should return FALSE for this, they can't receive NET messages
virtual bool IsFakeClient( void ) const { return true; }
virtual bool IsBot( void ) const { return true; }
virtual INextBot *MyNextBotPointer( void ) { return this; }
// this is valid because the templatized PlayerType must be derived from CBasePlayer, which is derived from CBaseCombatCharacter
virtual CBaseCombatCharacter *GetEntity( void ) const { return ( PlayerType * )this; }
virtual bool IsRemovedOnReset( void ) const { return false; } // remove this bot when the NextBot manager calls Reset
virtual bool IsDormantWhenDead( void ) const { return true; } // should this player-bot continue to update itself when dead (respawn logic, etc)
// allocate a bot and bind it to the edict
static CBasePlayer *AllocatePlayerEntity( edict_t *edict, const char *playerName );
//------------------------------------------------------------------------
// utility methods
float GetDistanceBetween( CBaseEntity *other ) const; // return distance between us and the given entity
bool IsDistanceBetweenLessThan( CBaseEntity *other, float range ) const; // return true if distance between is less than the given value
bool IsDistanceBetweenGreaterThan( CBaseEntity *other, float range ) const; // return true if distance between is greater than the given value
float GetDistanceBetween( const Vector &target ) const; // return distance between us and the given entity
bool IsDistanceBetweenLessThan( const Vector &target, float range ) const; // return true if distance between is less than the given value
bool IsDistanceBetweenGreaterThan( const Vector &target, float range ) const; // return true if distance between is greater than the given value
//------------------------------------------------------------------------
// INextBotPlayerInput
virtual void PressFireButton( float duration = -1.0f );
virtual void ReleaseFireButton( void );
virtual void PressAltFireButton( float duration = -1.0f );
virtual void ReleaseAltFireButton( void );
virtual void PressMeleeButton( float duration = -1.0f );
virtual void ReleaseMeleeButton( void );
virtual void PressUseButton( float duration = -1.0f );
virtual void ReleaseUseButton( void );
virtual void PressReloadButton( float duration = -1.0f );
virtual void ReleaseReloadButton( void );
virtual void PressForwardButton( float duration = -1.0f );
virtual void ReleaseForwardButton( void );
virtual void PressBackwardButton( float duration = -1.0f );
virtual void ReleaseBackwardButton( void );
virtual void PressLeftButton( float duration = -1.0f );
virtual void ReleaseLeftButton( void );
virtual void PressRightButton( float duration = -1.0f );
virtual void ReleaseRightButton( void );
virtual void PressJumpButton( float duration = -1.0f );
virtual void ReleaseJumpButton( void );
virtual void PressCrouchButton( float duration = -1.0f );
virtual void ReleaseCrouchButton( void );
virtual void PressWalkButton( float duration = -1.0f );
virtual void ReleaseWalkButton( void );
virtual void SetButtonScale( float forward, float right );
//------------------------------------------------------------------------
// Event hooks into NextBot system
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual int OnTakeDamage_Dying( const CTakeDamageInfo &info );
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void HandleAnimEvent( animevent_t *event );
virtual void OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea ); // invoked (by UpdateLastKnownArea) when we enter a new nav area (or it is reset to NULL)
virtual void Touch( CBaseEntity *other );
virtual void Weapon_Equip( CBaseCombatWeapon *weapon ); // for OnPickUp
virtual void Weapon_Drop( CBaseCombatWeapon *weapon, const Vector *target, const Vector *velocity ); // for OnDrop
virtual void OnMainActivityComplete( Activity newActivity, Activity oldActivity );
virtual void OnMainActivityInterrupted( Activity newActivity, Activity oldActivity );
//------------------------------------------------------------------------
bool IsAbleToAutoCenterOnLadders( void ) const;
public:
// begin INextBot ------------------------------------------------------------------------------------------------------------------
virtual void Update( void ); // (EXTEND) update internal state
protected:
int m_inputButtons; // this is still needed to guarantee each button press is captured at least once
int m_prevInputButtons;
CountdownTimer m_fireButtonTimer;
CountdownTimer m_meleeButtonTimer;
CountdownTimer m_useButtonTimer;
CountdownTimer m_reloadButtonTimer;
CountdownTimer m_forwardButtonTimer;
CountdownTimer m_backwardButtonTimer;
CountdownTimer m_leftButtonTimer;
CountdownTimer m_rightButtonTimer;
CountdownTimer m_jumpButtonTimer;
CountdownTimer m_crouchButtonTimer;
CountdownTimer m_walkButtonTimer;
CountdownTimer m_buttonScaleTimer;
IntervalTimer m_burningTimer; // how long since we were last burning
float m_forwardScale;
float m_rightScale;
};
template < typename PlayerType >
inline float NextBotPlayer< PlayerType >::GetDistanceBetween( CBaseEntity *other ) const
{
return (this->GetAbsOrigin() - other->GetAbsOrigin()).Length();
}
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenLessThan( CBaseEntity *other, float range ) const
{
return (this->GetAbsOrigin() - other->GetAbsOrigin()).IsLengthLessThan( range );
}
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenGreaterThan( CBaseEntity *other, float range ) const
{
return (this->GetAbsOrigin() - other->GetAbsOrigin()).IsLengthGreaterThan( range );
}
template < typename PlayerType >
inline float NextBotPlayer< PlayerType >::GetDistanceBetween( const Vector &target ) const
{
return (this->GetAbsOrigin() - target).Length();
}
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenLessThan( const Vector &target, float range ) const
{
return (this->GetAbsOrigin() - target).IsLengthLessThan( range );
}
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsDistanceBetweenGreaterThan( const Vector &target, float range ) const
{
return (this->GetAbsOrigin() - target).IsLengthGreaterThan( range );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressFireButton( float duration )
{
m_inputButtons |= IN_ATTACK;
m_fireButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseFireButton( void )
{
m_inputButtons &= ~IN_ATTACK;
m_fireButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressAltFireButton( float duration )
{
PressMeleeButton( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseAltFireButton( void )
{
ReleaseMeleeButton();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressMeleeButton( float duration )
{
m_inputButtons |= IN_ATTACK2;
m_meleeButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseMeleeButton( void )
{
m_inputButtons &= ~IN_ATTACK2;
m_meleeButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressUseButton( float duration )
{
m_inputButtons |= IN_USE;
m_useButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseUseButton( void )
{
m_inputButtons &= ~IN_USE;
m_useButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressReloadButton( float duration )
{
m_inputButtons |= IN_RELOAD;
m_reloadButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseReloadButton( void )
{
m_inputButtons &= ~IN_RELOAD;
m_reloadButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressJumpButton( float duration )
{
m_inputButtons |= IN_JUMP;
m_jumpButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseJumpButton( void )
{
m_inputButtons &= ~IN_JUMP;
m_jumpButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressCrouchButton( float duration )
{
m_inputButtons |= IN_DUCK;
m_crouchButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseCrouchButton( void )
{
m_inputButtons &= ~IN_DUCK;
m_crouchButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressWalkButton( float duration )
{
m_inputButtons |= IN_SPEED;
m_walkButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseWalkButton( void )
{
m_inputButtons &= ~IN_SPEED;
m_walkButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressForwardButton( float duration )
{
m_inputButtons |= IN_FORWARD;
m_forwardButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseForwardButton( void )
{
m_inputButtons &= ~IN_FORWARD;
m_forwardButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressBackwardButton( float duration )
{
m_inputButtons |= IN_BACK;
m_backwardButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseBackwardButton( void )
{
m_inputButtons &= ~IN_BACK;
m_backwardButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressLeftButton( float duration )
{
m_inputButtons |= IN_MOVELEFT;
m_leftButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseLeftButton( void )
{
m_inputButtons &= ~IN_MOVELEFT;
m_leftButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PressRightButton( float duration )
{
m_inputButtons |= IN_MOVERIGHT;
m_rightButtonTimer.Start( duration );
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::ReleaseRightButton( void )
{
m_inputButtons &= ~IN_MOVERIGHT;
m_rightButtonTimer.Invalidate();
}
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::SetButtonScale( float forward, float right )
{
m_forwardScale = forward;
m_rightScale = right;
m_buttonScaleTimer.Start( 0.01 );
}
//-----------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline NextBotPlayer< PlayerType >::NextBotPlayer( void )
{
m_prevInputButtons = 0;
m_inputButtons = 0;
m_burningTimer.Invalidate();
}
//-----------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline NextBotPlayer< PlayerType >::~NextBotPlayer()
{
}
//-----------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Spawn( void )
{
engine->SetFakeClientConVarValue( this->edict(), "cl_autohelp", "0" );
m_prevInputButtons = m_inputButtons = 0;
m_fireButtonTimer.Invalidate();
m_meleeButtonTimer.Invalidate();
m_useButtonTimer.Invalidate();
m_reloadButtonTimer.Invalidate();
m_forwardButtonTimer.Invalidate();
m_backwardButtonTimer.Invalidate();
m_leftButtonTimer.Invalidate();
m_rightButtonTimer.Invalidate();
m_jumpButtonTimer.Invalidate();
m_crouchButtonTimer.Invalidate();
m_walkButtonTimer.Invalidate();
m_buttonScaleTimer.Invalidate();
m_forwardScale = m_rightScale = 0.04;
m_burningTimer.Invalidate();
// reset first, because Spawn() may access various interfaces
INextBot::Reset();
BaseClass::Spawn();
}
//-----------------------------------------------------------------------------------------------------
inline void _NextBot_BuildUserCommand( CUserCmd *cmd, const QAngle &viewangles, float forwardmove, float sidemove, float upmove, int buttons, byte impulse )
{
Q_memset( cmd, 0, sizeof( CUserCmd ) );
cmd->command_number = gpGlobals->tickcount;
cmd->forwardmove = forwardmove;
cmd->sidemove = sidemove;
cmd->upmove = upmove;
cmd->buttons = buttons;
cmd->impulse = impulse;
VectorCopy( viewangles, cmd->viewangles );
cmd->random_seed = random->RandomInt( 0, 0x7fffffff );
}
//-----------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::PhysicsSimulate( void )
{
VPROF( "NextBotPlayer::PhysicsSimulate" );
if ( engine->IsPaused() )
{
// We're paused - don't add new commands
PlayerType::PhysicsSimulate();
return;
}
if ( ( IsDormantWhenDead() && PlayerType::m_lifeState == LIFE_DEAD ) || NextBotStop.GetBool() )
{
// death animation complete - nothing left to do except let PhysicsSimulate run PreThink etc
PlayerType::PhysicsSimulate();
return;
}
int inputButtons;
//
// Update bot behavior
//
if ( BeginUpdate() )
{
Update();
// build button bits
if ( !m_fireButtonTimer.IsElapsed() )
m_inputButtons |= IN_ATTACK;
if ( !m_meleeButtonTimer.IsElapsed() )
m_inputButtons |= IN_ATTACK2;
if ( !m_useButtonTimer.IsElapsed() )
m_inputButtons |= IN_USE;
if ( !m_reloadButtonTimer.IsElapsed() )
m_inputButtons |= IN_RELOAD;
if ( !m_forwardButtonTimer.IsElapsed() )
m_inputButtons |= IN_FORWARD;
if ( !m_backwardButtonTimer.IsElapsed() )
m_inputButtons |= IN_BACK;
if ( !m_leftButtonTimer.IsElapsed() )
m_inputButtons |= IN_MOVELEFT;
if ( !m_rightButtonTimer.IsElapsed() )
m_inputButtons |= IN_MOVERIGHT;
if ( !m_jumpButtonTimer.IsElapsed() )
m_inputButtons |= IN_JUMP;
if ( !m_crouchButtonTimer.IsElapsed() )
m_inputButtons |= IN_DUCK;
if ( !m_walkButtonTimer.IsElapsed() )
m_inputButtons |= IN_SPEED;
m_prevInputButtons = m_inputButtons;
inputButtons = m_inputButtons;
EndUpdate();
}
else
{
// HACK: Smooth out body animations
GetBodyInterface()->Update();
// keep buttons pressed between Update() calls (m_prevInputButtons),
// and include any button presses that occurred this tick (m_inputButtons).
inputButtons = m_prevInputButtons | m_inputButtons;
}
//
// Convert NextBot locomotion and posture into
// player commands
//
IBody *body = GetBodyInterface();
ILocomotion *mover = GetLocomotionInterface();
if ( body->IsActualPosture( IBody::CROUCH ) )
{
inputButtons |= IN_DUCK;
}
float forwardSpeed = 0.0f;
float strafeSpeed = 0.0f;
float verticalSpeed = ( m_inputButtons & IN_JUMP ) ? mover->GetRunSpeed() : 0.0f;
if ( inputButtons & IN_FORWARD )
{
forwardSpeed = mover->GetRunSpeed();
}
else if ( inputButtons & IN_BACK )
{
forwardSpeed = -mover->GetRunSpeed();
}
if ( inputButtons & IN_MOVELEFT )
{
strafeSpeed = -mover->GetRunSpeed();
}
else if ( inputButtons & IN_MOVERIGHT )
{
strafeSpeed = mover->GetRunSpeed();
}
if ( NextBotPlayerWalk.GetBool() )
{
inputButtons |= IN_SPEED;
}
if ( NextBotPlayerCrouch.GetBool() )
{
inputButtons |= IN_DUCK;
}
if ( !m_buttonScaleTimer.IsElapsed() )
{
forwardSpeed = mover->GetRunSpeed() * m_forwardScale;
strafeSpeed = mover->GetRunSpeed() * m_rightScale;
}
if ( !NextBotPlayerMove.GetBool() )
{
inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP );
forwardSpeed = 0.0f;
strafeSpeed = 0.0f;
verticalSpeed = 0.0f;
}
QAngle angles = this->EyeAngles();
#ifdef TERROR
if ( IsStunned() )
{
inputButtons &= ~(IN_FORWARD | IN_BACK | IN_MOVELEFT | IN_MOVERIGHT | IN_JUMP | IN_DUCK );
}
// "Look" in the direction we're climbing/stumbling etc. We can't do anything anyway, and it
// keeps motion extraction working.
if ( IsRenderYawOverridden() && IsMotionControlledXY( GetMainActivity() ) )
{
angles[YAW] = GetOverriddenRenderYaw();
}
#endif
// construct a "command" to move the player
CUserCmd userCmd;
_NextBot_BuildUserCommand( &userCmd, angles, forwardSpeed, strafeSpeed, verticalSpeed, inputButtons, 0 );
#ifdef TERROR
AvoidPlayers( &userCmd );
#endif
// allocate a new command and add it to the player's list of command to process
this->ProcessUsercmds( &userCmd, 1, 1, 0, false );
m_inputButtons = 0;
// actually execute player commands and do player physics
PlayerType::PhysicsSimulate();
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
{
// propagate into NextBot responders
INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
BaseClass::OnNavAreaChanged( enteredArea, leftArea );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Touch( CBaseEntity *other )
{
if ( ShouldTouch( other ) )
{
// propagate touch into NextBot event responders
trace_t result;
result = this->GetTouchTrace();
OnContact( other, &result );
}
BaseClass::Touch( other );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Weapon_Equip( CBaseCombatWeapon *weapon )
{
#ifdef TERROR
// TODO: Reimplement GetDroppingPlayer() into GetLastOwner()
OnPickUp( weapon, weapon->GetDroppingPlayer() );
#else
OnPickUp( weapon, NULL );
#endif
BaseClass::Weapon_Equip( weapon );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Weapon_Drop( CBaseCombatWeapon *weapon, const Vector *target, const Vector *velocity )
{
OnDrop( weapon );
BaseClass::Weapon_Drop( weapon, target, velocity );
}
//--------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::OnMainActivityComplete( Activity newActivity, Activity oldActivity )
{
#ifdef TERROR
BaseClass::OnMainActivityComplete( newActivity, oldActivity );
#endif
OnAnimationActivityComplete( oldActivity );
}
//--------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::OnMainActivityInterrupted( Activity newActivity, Activity oldActivity )
{
#ifdef TERROR
BaseClass::OnMainActivityInterrupted( newActivity, oldActivity );
#endif
OnAnimationActivityInterrupted( oldActivity );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Update( void )
{
// don't spend CPU updating if this Survivor is dead
if ( ( this->IsAlive() || !IsDormantWhenDead() ) && !NextBotPlayerStop.GetBool() )
{
INextBot::Update();
}
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline bool NextBotPlayer< PlayerType >::IsAbleToAutoCenterOnLadders( void ) const
{
const ILocomotion *locomotion = GetLocomotionInterface();
return locomotion && locomotion->IsAbleToAutoCenterOnLadder();
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline int NextBotPlayer< PlayerType >::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
if ( info.GetDamageType() & DMG_BURN )
{
if ( !m_burningTimer.HasStarted() || m_burningTimer.IsGreaterThen( 1.0f ) )
{
// emit ignite event periodically as long as we are burning
OnIgnite();
m_burningTimer.Start();
}
}
// propagate event to components
OnInjured( info );
return BaseClass::OnTakeDamage_Alive( info );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline int NextBotPlayer< PlayerType >::OnTakeDamage_Dying( const CTakeDamageInfo &info )
{
if ( info.GetDamageType() & DMG_BURN )
{
if ( !m_burningTimer.HasStarted() || m_burningTimer.IsGreaterThen( 1.0f ) )
{
// emit ignite event periodically as long as we are burning
OnIgnite();
m_burningTimer.Start();
}
}
// propagate event to components
OnInjured( info );
return BaseClass::OnTakeDamage_Dying( info );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::Event_Killed( const CTakeDamageInfo &info )
{
// propagate event to my components
OnKilled( info );
BaseClass::Event_Killed( info );
}
//----------------------------------------------------------------------------------------------------------
template < typename PlayerType >
inline void NextBotPlayer< PlayerType >::HandleAnimEvent( animevent_t *event )
{
// propagate event to components
OnAnimationEvent( event );
BaseClass::HandleAnimEvent( event );
}
#endif // _NEXT_BOT_PLAYER_H_
@@ -0,0 +1,805 @@
// NextBotPlayerBody.cpp
// Implementation of Body interface for CBasePlayer-derived classes
// Author: Michael Booth, October 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "NextBot.h"
#include "NextBotPlayerBody.h"
#include "NextBotPlayer.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar NextBotSaccadeTime( "nb_saccade_time", "0.1", FCVAR_CHEAT );
ConVar NextBotSaccadeSpeed( "nb_saccade_speed", "1000", FCVAR_CHEAT );
ConVar NextBotHeadAimSteadyMaxRate( "nb_head_aim_steady_max_rate", "100", FCVAR_CHEAT );
ConVar NextBotHeadAimSettleDuration( "nb_head_aim_settle_duration", "0.3", FCVAR_CHEAT );
//-----------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the fire button.
*/
void PressFireButtonReply::OnSuccess( INextBot *bot )
{
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
if ( playerInput )
{
playerInput->PressFireButton();
}
}
//-----------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the alternate fire button.
*/
void PressAltFireButtonReply::OnSuccess( INextBot *bot )
{
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
if ( playerInput )
{
playerInput->PressMeleeButton();
}
}
//-----------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the jump button.
*/
void PressJumpButtonReply::OnSuccess( INextBot *bot )
{
INextBotPlayerInput *playerInput = dynamic_cast< INextBotPlayerInput * >( bot->GetEntity() );
if ( playerInput )
{
playerInput->PressJumpButton();
}
}
//-----------------------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------------------
PlayerBody::PlayerBody( INextBot *bot ) : IBody( bot )
{
m_player = static_cast< CBasePlayer * >( bot->GetEntity() );
}
//-----------------------------------------------------------------------------------------------
PlayerBody::~PlayerBody()
{
}
//-----------------------------------------------------------------------------------------------
/**
* reset to initial state
*/
void PlayerBody::Reset( void )
{
m_posture = STAND;
m_lookAtPos = vec3_origin;
m_lookAtSubject = NULL;
m_lookAtReplyWhenAimed = NULL;
m_lookAtPriority = BORING;
m_lookAtExpireTimer.Invalidate();
m_lookAtDurationTimer.Invalidate();
m_isSightedIn = false;
m_hasBeenSightedIn = false;
m_headSteadyTimer.Invalidate();
m_yawRate = 0.0f;
m_pitchRate = 0.0f;
m_priorAngles = vec3_angle;
}
static ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
//-----------------------------------------------------------------------------------------------
/**
* Update internal state.
* Do this every tick to keep head aims smooth and accurate
*/
void PlayerBody::Upkeep( void )
{
// If mimicking the player, don't modify the view angles.
static ConVarRef bot_mimic( "bot_mimic" );
if ( bot_mimic.IsValid() && bot_mimic.GetBool() )
return;
const float deltaT = gpGlobals->frametime;
if ( deltaT < 0.00001f )
{
return;
}
CBasePlayer *player = ( CBasePlayer * )GetBot()->GetEntity();
// get current view angles
QAngle currentAngles = player->EyeAngles() + player->GetPunchAngle();
// track when our head is "steady"
bool isSteady = true;
float actualPitchRate = AngleDiff( currentAngles.x, m_priorAngles.x );
if ( abs( actualPitchRate ) > NextBotHeadAimSteadyMaxRate.GetFloat() * deltaT )
{
isSteady = false;
}
else
{
float actualYawRate = AngleDiff( currentAngles.y, m_priorAngles.y );
if ( abs( actualYawRate ) > NextBotHeadAimSteadyMaxRate.GetFloat() * deltaT )
{
isSteady = false;
}
}
if ( isSteady )
{
if ( !m_headSteadyTimer.HasStarted() )
{
m_headSteadyTimer.Start();
}
}
else
{
m_headSteadyTimer.Invalidate();
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
if ( IsHeadSteady() )
{
const float maxTime = 3.0f;
float t = GetHeadSteadyDuration() / maxTime;
t = clamp( t, 0, 1.0f );
NDebugOverlay::Circle( player->EyePosition(), t * 10.0f, 0, 255, 0, 255, true, 2.0f * deltaT );
}
}
m_priorAngles = currentAngles;
// if our current look-at has expired, don't change our aim further
if ( m_hasBeenSightedIn && m_lookAtExpireTimer.IsElapsed() )
{
return;
}
// if we have a subject, update lookat point
CBaseEntity *subject = m_lookAtSubject;
if ( subject )
{
if ( subject->MyCombatCharacterPointer() )
{
m_lookAtPos = GetBot()->GetIntentionInterface()->SelectTargetPoint( GetBot(), subject->MyCombatCharacterPointer() );
}
else
{
m_lookAtPos = subject->WorldSpaceCenter();
}
m_lookAtPos += GetHeadAimSubjectLeadTime() * subject->GetAbsVelocity();
}
// aim view towards last look at point
Vector to = m_lookAtPos - GetEyePosition();
to.NormalizeInPlace();
QAngle desiredAngles;
VectorAngles( to, desiredAngles );
QAngle angles;
const Vector &forward = GetViewVector();
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
NDebugOverlay::Line( GetEyePosition(), GetEyePosition() + 100.0f * forward, 255, 255, 0, false, 2.0f * deltaT );
float thickness = isSteady ? 2.0f : 3.0f;
int g = subject ? 255 : 0;
NDebugOverlay::HorzArrow( GetEyePosition(), m_lookAtPos, thickness, 0, g, 255, 255, false, 2.0f * deltaT );
}
const float onTargetTolerance = 0.98f;
float dot = DotProduct( forward, to );
if ( dot > onTargetTolerance )
{
// on target
m_isSightedIn = true;
m_hasBeenSightedIn = true;
if ( m_lookAtReplyWhenAimed )
{
m_lookAtReplyWhenAimed->OnSuccess( GetBot() );
m_lookAtReplyWhenAimed = NULL;
}
}
else
{
// off target
m_isSightedIn = false;
}
// rotate view at a rate proportional to how far we have to turn
// max rate if we need to turn around
// want first derivative continuity of rate as our aim hits to avoid pop
float approachRate = GetMaxHeadAngularVelocity();
const float easeOut = 0.7f;
if ( dot > easeOut )
{
float t = RemapVal( dot, easeOut, 1.0f, 1.0f, 0.02f );
const float halfPI = 1.57f;
approachRate *= sin( halfPI * t );
}
const float easeInTime = 0.25f;
if ( m_lookAtDurationTimer.GetElapsedTime() < easeInTime )
{
approachRate *= m_lookAtDurationTimer.GetElapsedTime() / easeInTime;
}
angles.y = ApproachAngle( desiredAngles.y, currentAngles.y, approachRate * deltaT );
angles.x = ApproachAngle( desiredAngles.x, currentAngles.x, approachRate * deltaT );
angles.z = 0.0f;
// back out "punch angle"
angles -= player->GetPunchAngle();
angles.x = AngleNormalize( angles.x );
angles.y = AngleNormalize( angles.y );
player->SnapEyeAngles( angles );
}
//-----------------------------------------------------------------------------------------------
bool PlayerBody::SetPosition( const Vector &pos )
{
m_player->SetAbsOrigin( pos );
return true;
}
//-----------------------------------------------------------------------------------------------
/**
* Return the eye position of the bot in world coordinates
*/
const Vector &PlayerBody::GetEyePosition( void ) const
{
m_eyePos = m_player->EyePosition();
return m_eyePos;
}
CBaseEntity *PlayerBody::GetEntity( void )
{
return m_player;
}
//-----------------------------------------------------------------------------------------------
/**
* Return the view unit direction vector in world coordinates
*/
const Vector &PlayerBody::GetViewVector( void ) const
{
m_player->EyeVectors( &m_viewVector );
return m_viewVector;
}
//-----------------------------------------------------------------------------------------------
/**
* Aim the bot's head towards the given goal
*/
void PlayerBody::AimHeadTowards( const Vector &lookAtPos, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
{
if ( duration <= 0.0f )
{
duration = 0.1f;
}
// don't spaz our aim around
if ( m_lookAtPriority == priority )
{
if ( !IsHeadSteady() || GetHeadSteadyDuration() < NextBotHeadAimSettleDuration.GetFloat() )
{
// we're still finishing a look-at at the same priority
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At rejected - previous aim not settled\n",
gpGlobals->curtime,
m_player->GetPlayerName() );
}
return;
}
}
// don't short-circuit if "sighted in" to avoid rapid view jitter
if ( m_lookAtPriority > priority && !m_lookAtExpireTimer.IsElapsed() )
{
// higher priority lookat still ongoing
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At rejected - higher priority aim in progress\n",
gpGlobals->curtime,
m_player->GetPlayerName() );
}
return;
}
if ( m_lookAtReplyWhenAimed )
{
// in-process aim was interrupted
m_lookAtReplyWhenAimed->OnFail( GetBot(), INextBotReply::INTERRUPTED );
}
m_lookAtReplyWhenAimed = replyWhenAimed;
m_lookAtExpireTimer.Start( duration );
// if given the same point, just update priority
const float epsilon = 1.0f;
if ( ( m_lookAtPos - lookAtPos ).IsLengthLessThan( epsilon ) )
{
m_lookAtPriority = priority;
return;
}
// new look-at point
m_lookAtPos = lookAtPos;
m_lookAtSubject = NULL;
m_lookAtPriority = priority;
m_lookAtDurationTimer.Start();
m_isSightedIn = false;
m_hasBeenSightedIn = false;
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
NDebugOverlay::Cross3D( lookAtPos, 2.0f, 255, 255, 100, true, 2.0f * duration );
char *priName = "";
switch( priority )
{
case BORING: priName = "BORING"; break;
case INTERESTING: priName = "INTERESTING"; break;
case IMPORTANT: priName = "IMPORTANT"; break;
case CRITICAL: priName = "CRITICAL"; break;
}
ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At ( %g, %g, %g ) for %3.2f s, Pri = %s, Reason = %s\n",
gpGlobals->curtime,
m_player->GetPlayerName(),
lookAtPos.x, lookAtPos.y, lookAtPos.z,
duration,
priName,
( reason ) ? reason : "" );
}
}
//-----------------------------------------------------------------------------------------------
/**
* Aim the bot's head towards the given goal
*/
void PlayerBody::AimHeadTowards( CBaseEntity *subject, LookAtPriorityType priority, float duration, INextBotReply *replyWhenAimed, const char *reason )
{
if ( duration <= 0.0f )
{
duration = 0.1f;
}
if ( subject == NULL )
{
return;
}
// don't spaz our aim around
if ( m_lookAtPriority == priority )
{
if ( !IsHeadSteady() || GetHeadSteadyDuration() < NextBotHeadAimSettleDuration.GetFloat() )
{
// we're still finishing a look-at at the same priority
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At rejected - previous aim not settled\n",
gpGlobals->curtime,
m_player->GetPlayerName() );
}
return;
}
}
// don't short-circuit if "sighted in" to avoid rapid view jitter
if ( m_lookAtPriority > priority && !m_lookAtExpireTimer.IsElapsed() )
{
// higher priority lookat still ongoing
if ( replyWhenAimed )
{
replyWhenAimed->OnFail( GetBot(), INextBotReply::DENIED );
}
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
ConColorMsg( Color( 255, 0, 0, 255 ), "%3.2f: %s Look At rejected - higher priority aim in progress\n",
gpGlobals->curtime,
m_player->GetPlayerName() );
}
return;
}
if ( m_lookAtReplyWhenAimed )
{
// in-process aim was interrupted
m_lookAtReplyWhenAimed->OnFail( GetBot(), INextBotReply::INTERRUPTED );
}
m_lookAtReplyWhenAimed = replyWhenAimed;
m_lookAtExpireTimer.Start( duration );
// if given the same subject, just update priority
if ( subject == m_lookAtSubject )
{
m_lookAtPriority = priority;
return;
}
// new subject
m_lookAtSubject = subject;
#ifdef REFACTOR_FOR_CLIENT_SIDE_EYE_TRACKING
CBasePlayer *pMyPlayer = static_cast< CBasePlayer * >( GetEntity() );
if ( subject->IsPlayer() )
{
// looking at a player, look at their eye position
TerrorPlayer *pMyTarget = ToTerrorPlayer( subject );
m_lookAtPos = subject->EyePosition();
if(pMyPlayer)
{
pMyPlayer->SetLookatPlayer( pMyTarget );
}
}
else
{
// not looking at a player
m_lookAtPos = subject->WorldSpaceCenter();
if(pMyPlayer)
{
pMyPlayer->SetLookatPlayer( NULL );
}
}
#endif
m_lookAtPriority = priority;
m_lookAtDurationTimer.Start();
m_isSightedIn = false;
m_hasBeenSightedIn = false;
if ( GetBot()->IsDebugging( NEXTBOT_LOOK_AT ) )
{
NDebugOverlay::Cross3D( m_lookAtPos, 2.0f, 100, 100, 100, true, duration );
char *priName = "";
switch( priority )
{
case BORING: priName = "BORING"; break;
case INTERESTING: priName = "INTERESTING"; break;
case IMPORTANT: priName = "IMPORTANT"; break;
case CRITICAL: priName = "CRITICAL"; break;
}
ConColorMsg( Color( 255, 100, 0, 255 ), "%3.2f: %s Look At subject %s for %3.2f s, Pri = %s, Reason = %s\n",
gpGlobals->curtime,
m_player->GetPlayerName(),
subject->GetClassname(),
duration,
priName,
( reason ) ? reason : "" );
}
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if head is not rapidly turning to look somewhere else
*/
bool PlayerBody::IsHeadSteady( void ) const
{
return m_headSteadyTimer.HasStarted();
}
//-----------------------------------------------------------------------------------------------
/**
* Return the duration that the bot's head has been on-target
*/
float PlayerBody::GetHeadSteadyDuration( void ) const
{
// return ( IsHeadAimingOnTarget() ) ? m_headSteadyTimer.GetElapsedTime() : 0.0f;
return m_headSteadyTimer.HasStarted() ? m_headSteadyTimer.GetElapsedTime() : 0.0f;
}
//-----------------------------------------------------------------------------------------------
float PlayerBody::GetMaxHeadAngularVelocity( void ) const
{
return NextBotSaccadeSpeed.GetFloat();
}
//-----------------------------------------------------------------------------------------------
bool PlayerBody::StartActivity( Activity act, unsigned int flags )
{
// player animation state is controlled on the client
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Return currently animating activity
*/
Activity PlayerBody::GetActivity( void ) const
{
return ACT_INVALID;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if currently animating activity matches the given one
*/
bool PlayerBody::IsActivity( Activity act ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if currently animating activity has any of the given flags
*/
bool PlayerBody::HasActivityType( unsigned int flags ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Request a posture change
*/
void PlayerBody::SetDesiredPosture( PostureType posture )
{
m_posture = posture;
}
//-----------------------------------------------------------------------------------------------
/**
* Get posture body is trying to assume
*/
IBody::PostureType PlayerBody::GetDesiredPosture( void ) const
{
return m_posture;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body is trying to assume this posture
*/
bool PlayerBody::IsDesiredPosture( PostureType posture ) const
{
return ( posture == m_posture );
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body's actual posture matches its desired posture
*/
bool PlayerBody::IsInDesiredPosture( void ) const
{
return true;
}
//-----------------------------------------------------------------------------------------------
/**
* Return body's current actual posture
*/
IBody::PostureType PlayerBody::GetActualPosture( void ) const
{
return m_posture;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body is actually in the given posture
*/
bool PlayerBody::IsActualPosture( PostureType posture ) const
{
return ( posture == m_posture );
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body's current posture allows it to move around the world
*/
bool PlayerBody::IsPostureMobile( void ) const
{
return true;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body's posture is in the process of changing to new posture
*/
bool PlayerBody::IsPostureChanging( void ) const
{
return false;
}
//-----------------------------------------------------------------------------------------------
/**
* Arousal level change
*/
void PlayerBody::SetArousal( ArousalType arousal )
{
m_arousal = arousal;
}
//-----------------------------------------------------------------------------------------------
/**
* Get arousal level
*/
IBody::ArousalType PlayerBody::GetArousal( void ) const
{
return m_arousal;
}
//-----------------------------------------------------------------------------------------------
/**
* Return true if body is at this arousal level
*/
bool PlayerBody::IsArousal( ArousalType arousal ) const
{
return ( arousal == m_arousal );
}
//-----------------------------------------------------------------------------------------------
/**
* Width of bot's collision hull in XY plane
*/
float PlayerBody::GetHullWidth( void ) const
{
return VEC_HULL_MAX.x - VEC_HULL_MIN.x;
}
//-----------------------------------------------------------------------------------------------
/**
* Height of bot's current collision hull based on posture
*/
float PlayerBody::GetHullHeight( void ) const
{
if ( m_posture == CROUCH )
{
return GetCrouchHullHeight();
}
return GetStandHullHeight();
}
//-----------------------------------------------------------------------------------------------
/**
* Height of bot's collision hull when standing
*/
float PlayerBody::GetStandHullHeight( void ) const
{
return VEC_HULL_MAX.z - VEC_HULL_MIN.z;
}
//-----------------------------------------------------------------------------------------------
/**
* Height of bot's collision hull when crouched
*/
float PlayerBody::GetCrouchHullHeight( void ) const
{
return VEC_DUCK_HULL_MAX.z - VEC_DUCK_HULL_MIN.z;
}
//-----------------------------------------------------------------------------------------------
/**
* Return current collision hull minimums based on actual body posture
*/
const Vector &PlayerBody::GetHullMins( void ) const
{
if ( m_posture == CROUCH )
{
m_hullMins = VEC_DUCK_HULL_MIN;
}
else
{
m_hullMins = VEC_HULL_MIN;
}
return m_hullMins;
}
//-----------------------------------------------------------------------------------------------
/**
* Return current collision hull maximums based on actual body posture
*/
const Vector &PlayerBody::GetHullMaxs( void ) const
{
if ( m_posture == CROUCH )
{
m_hullMaxs = VEC_DUCK_HULL_MAX;
}
else
{
m_hullMaxs = VEC_HULL_MAX;
}
return m_hullMaxs;
}
//-----------------------------------------------------------------------------------------------
/**
* Return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
*/
unsigned int PlayerBody::GetSolidMask( void ) const
{
return ( m_player ) ? m_player->PlayerSolidMask() : MASK_PLAYERSOLID;
}
@@ -0,0 +1,148 @@
// NextBotPlayerBody.h
// Control and information about the bot's body state (posture, animation state, etc)
// Author: Michael Booth, October 2006
// Copyright (c) 2006 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_PLAYER_BODY_H_
#define _NEXT_BOT_PLAYER_BODY_H_
#include "NextBotBodyInterface.h"
//----------------------------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the fire button.
*/
class PressFireButtonReply : public INextBotReply
{
public:
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
};
//----------------------------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the alt-fire button.
*/
class PressAltFireButtonReply : public INextBotReply
{
public:
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
};
//----------------------------------------------------------------------------------------------------------------
/**
* A useful reply for IBody::AimHeadTowards. When the
* head is aiming on target, press the jump button.
*/
class PressJumpButtonReply : public INextBotReply
{
public:
virtual void OnSuccess( INextBot *bot ); // invoked when process completed successfully
};
//----------------------------------------------------------------------------------------------------------------
/**
* The interface for control and information about the bot's body state (posture, animation state, etc)
*/
class PlayerBody : public IBody
{
public:
PlayerBody( INextBot *bot );
virtual ~PlayerBody();
virtual void Reset( void ); // reset to initial state
virtual void Upkeep( void ); // lightweight update guaranteed to occur every server tick
virtual bool SetPosition( const Vector &pos );
virtual const Vector &GetEyePosition( void ) const; // return the eye position of the bot in world coordinates
virtual const Vector &GetViewVector( void ) const; // return the view unit direction vector in world coordinates
virtual void AimHeadTowards( const Vector &lookAtPos,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // aim the bot's head towards the given goal
virtual void AimHeadTowards( CBaseEntity *subject,
LookAtPriorityType priority = BORING,
float duration = 0.0f,
INextBotReply *replyWhenAimed = NULL,
const char *reason = NULL ); // continually aim the bot's head towards the given subject
virtual bool IsHeadAimingOnTarget( void ) const; // return true if the bot's head has achieved its most recent lookat target
virtual bool IsHeadSteady( void ) const; // return true if head is not rapidly turning to look somewhere else
virtual float GetHeadSteadyDuration( void ) const; // return the duration that the bot's head has been on-target
virtual float GetMaxHeadAngularVelocity( void ) const; // return max turn rate of head in degrees/second
virtual bool StartActivity( Activity act, unsigned int flags );
virtual Activity GetActivity( void ) const; // return currently animating activity
virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one
virtual bool HasActivityType( unsigned int flags ) const; // return true if currently animating activity has any of the given flags
virtual void SetDesiredPosture( PostureType posture ); // request a posture change
virtual PostureType GetDesiredPosture( void ) const; // get posture body is trying to assume
virtual bool IsDesiredPosture( PostureType posture ) const; // return true if body is trying to assume this posture
virtual bool IsInDesiredPosture( void ) const; // return true if body's actual posture matches its desired posture
virtual PostureType GetActualPosture( void ) const; // return body's current actual posture
virtual bool IsActualPosture( PostureType posture ) const; // return true if body is actually in the given posture
virtual bool IsPostureMobile( void ) const; // return true if body's current posture allows it to move around the world
virtual bool IsPostureChanging( void ) const; // return true if body's posture is in the process of changing to new posture
virtual void SetArousal( ArousalType arousal ); // arousal level change
virtual ArousalType GetArousal( void ) const; // get arousal level
virtual bool IsArousal( ArousalType arousal ) const; // return true if body is at this arousal level
virtual float GetHullWidth( void ) const; // width of bot's collision hull in XY plane
virtual float GetHullHeight( void ) const; // height of bot's current collision hull based on posture
virtual float GetStandHullHeight( void ) const; // height of bot's collision hull when standing
virtual float GetCrouchHullHeight( void ) const; // height of bot's collision hull when crouched
virtual const Vector &GetHullMins( void ) const; // return current collision hull minimums based on actual body posture
virtual const Vector &GetHullMaxs( void ) const; // return current collision hull maximums based on actual body posture
virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface)
virtual CBaseEntity *GetEntity( void ); // get the entity
private:
CBasePlayer *m_player;
PostureType m_posture;
ArousalType m_arousal;
mutable Vector m_eyePos; // for use with GetEyePosition() ONLY
mutable Vector m_viewVector; // for use with GetViewVector() ONLY
mutable Vector m_hullMins; // for use with GetHullMins() ONLY
mutable Vector m_hullMaxs; // for use with GetHullMaxs() ONLY
Vector m_lookAtPos; // if m_lookAtSubject is non-NULL, it continually overwrites this position with its own
EHANDLE m_lookAtSubject;
LookAtPriorityType m_lookAtPriority;
CountdownTimer m_lookAtExpireTimer; // how long until this lookat expired
IntervalTimer m_lookAtDurationTimer; // how long have we been looking at this target
INextBotReply *m_lookAtReplyWhenAimed;
bool m_isSightedIn; // true if we are looking at our last lookat target
bool m_hasBeenSightedIn; // true if we have hit the current lookat target
float m_yawRate;
float m_pitchRate;
IntervalTimer m_headSteadyTimer;
QAngle m_priorAngles; // last update's head angles
QAngle m_desiredAngles;
};
inline bool PlayerBody::IsHeadAimingOnTarget( void ) const
{
return m_isSightedIn;
}
#endif // _NEXT_BOT_PLAYER_BODY_H_
@@ -0,0 +1,826 @@
// NextBotPlayerLocomotion.cpp
// Implementation of Locomotion interface for CBasePlayer-derived classes
// Author: Michael Booth, November 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#include "cbase.h"
#include "nav_mesh.h"
#include "in_buttons.h"
#include "NextBot.h"
#include "NextBotUtil.h"
#include "NextBotPlayer.h"
#include "NextBotPlayerLocomotion.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar NextBotPlayerMoveDirect( "nb_player_move_direct", "0" );
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::PlayerLocomotion( INextBot *bot ) : ILocomotion( bot )
{
m_player = NULL;
Reset();
}
//-----------------------------------------------------------------------------------------------------
/**
* Reset locomotor to initial state
*/
void PlayerLocomotion::Reset( void )
{
m_player = static_cast< CBasePlayer * >( GetBot()->GetEntity() );
m_isJumping = false;
m_isClimbingUpToLedge = false;
m_isJumpingAcrossGap = false;
m_hasLeftTheGround = false;
m_desiredSpeed = 0.0f;
m_ladderState = NO_LADDER;
m_ladderInfo = NULL;
m_ladderDismountGoal = NULL;
m_ladderTimer.Invalidate();
m_minSpeedLimit = 0.0f;
m_maxSpeedLimit = 9999999.9f;
BaseClass::Reset();
}
//-----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::TraverseLadder( void )
{
switch( m_ladderState )
{
case APPROACHING_ASCENDING_LADDER:
m_ladderState = ApproachAscendingLadder();
return true;
case APPROACHING_DESCENDING_LADDER:
m_ladderState = ApproachDescendingLadder();
return true;
case ASCENDING_LADDER:
m_ladderState = AscendLadder();
return true;
case DESCENDING_LADDER:
m_ladderState = DescendLadder();
return true;
case DISMOUNTING_LADDER_TOP:
m_ladderState = DismountLadderTop();
return true;
case DISMOUNTING_LADDER_BOTTOM:
m_ladderState = DismountLadderBottom();
return true;
case NO_LADDER:
default:
m_ladderInfo = NULL;
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// on ladder and don't want to be
GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
}
return false;
}
return true;
}
//-----------------------------------------------------------------------------------------------------
/**
* We're close, but not yet on, this ladder - approach it
*/
PlayerLocomotion::LadderState PlayerLocomotion::ApproachAscendingLadder( void )
{
if ( m_ladderInfo == NULL )
{
return NO_LADDER;
}
// sanity check - are we already at the end of this ladder?
if ( GetFeet().z >= m_ladderInfo->m_top.z - GetStepHeight() )
{
m_ladderTimer.Start( 2.0f );
return DISMOUNTING_LADDER_TOP;
}
// sanity check - are we too far below this ladder to reach it?
if ( GetFeet().z <= m_ladderInfo->m_bottom.z - GetMaxJumpHeight() )
{
return NO_LADDER;
}
FaceTowards( m_ladderInfo->m_bottom );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( m_ladderInfo->m_bottom, 9999999.9f );
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// we're on the ladder
return ASCENDING_LADDER;
}
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Approach ascending ladder", 0.1f, 255, 255, 255, 255 );
}
return APPROACHING_ASCENDING_LADDER;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::ApproachDescendingLadder( void )
{
if ( m_ladderInfo == NULL )
{
return NO_LADDER;
}
// sanity check - are we already at the end of this ladder?
if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetMaxJumpHeight() )
{
m_ladderTimer.Start( 2.0f );
return DISMOUNTING_LADDER_BOTTOM;
}
Vector mountPoint = m_ladderInfo->m_top + 0.25f * GetBot()->GetBodyInterface()->GetHullWidth() * m_ladderInfo->GetNormal();
Vector to = mountPoint - GetFeet();
to.z = 0.0f;
float mountRange = to.NormalizeInPlace();
Vector moveGoal;
const float veryClose = 10.0f;
if ( mountRange < veryClose )
{
// we're right at the ladder - just keep moving forward until we grab it
const Vector &forward = GetMotionVector();
moveGoal = GetFeet() + 100.0f * forward;
}
else
{
if ( DotProduct( to, m_ladderInfo->GetNormal() ) < 0.0f )
{
// approaching front of downward ladder
// ##
// ->+ ##
// | ##
// | ##
// | ##
// <-+ ##
// ######
//
moveGoal = m_ladderInfo->m_top - 100.0f * m_ladderInfo->GetNormal();
}
else
{
// approaching back of downward ladder
//
// ->+
// ##|
// ##|
// ##+-->
// ######
//
moveGoal = m_ladderInfo->m_top + 100.0f * m_ladderInfo->GetNormal();
}
}
FaceTowards( moveGoal );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( moveGoal, 9999999.9f );
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// we're on the ladder
return DESCENDING_LADDER;
}
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Approach descending ladder", 0.1f, 255, 255, 255, 255 );
}
return APPROACHING_DESCENDING_LADDER;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::AscendLadder( void )
{
if ( m_ladderInfo == NULL )
{
return NO_LADDER;
}
if ( GetBot()->GetEntity()->GetMoveType() != MOVETYPE_LADDER )
{
// slipped off ladder
m_ladderInfo = NULL;
return NO_LADDER;
}
if ( GetFeet().z >= m_ladderInfo->m_top.z )
{
// reached top of ladder
m_ladderTimer.Start( 2.0f );
return DISMOUNTING_LADDER_TOP;
}
// climb up this ladder - look up
Vector goal = GetFeet() + 100.0f * ( -m_ladderInfo->GetNormal() + Vector( 0, 0, 2 ) );
GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( goal, 9999999.9f );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Ascend", 0.1f, 255, 255, 255, 255 );
}
return ASCENDING_LADDER;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::DescendLadder( void )
{
if ( m_ladderInfo == NULL )
{
return NO_LADDER;
}
if ( GetBot()->GetEntity()->GetMoveType() != MOVETYPE_LADDER )
{
// slipped off ladder
m_ladderInfo = NULL;
return NO_LADDER;
}
if ( GetFeet().z <= m_ladderInfo->m_bottom.z + GetBot()->GetLocomotionInterface()->GetStepHeight() )
{
// reached bottom of ladder
m_ladderTimer.Start( 2.0f );
return DISMOUNTING_LADDER_BOTTOM;
}
// climb down this ladder - look down
Vector goal = GetFeet() + 100.0f * ( m_ladderInfo->GetNormal() + Vector( 0, 0, -2 ) );
GetBot()->GetBodyInterface()->AimHeadTowards( goal, IBody::MANDATORY, 0.1f, NULL, "Ladder" );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( goal, 9999999.9f );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Descend", 0.1f, 255, 255, 255, 255 );
}
return DESCENDING_LADDER;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::DismountLadderTop( void )
{
if ( m_ladderInfo == NULL || m_ladderTimer.IsElapsed() )
{
m_ladderInfo = NULL;
return NO_LADDER;
}
IBody *body = GetBot()->GetBodyInterface();
Vector toGoal = m_ladderDismountGoal->GetCenter() - GetFeet();
toGoal.z = 0.0f;
float range = toGoal.NormalizeInPlace();
toGoal.z = 1.0f;
body->AimHeadTowards( body->GetEyePosition() + 100.0f * toGoal, IBody::MANDATORY, 0.1f, NULL, "Ladder dismount" );
// it is important to approach precisely, so use a very large weight to wash out all other Approaches
Approach( GetFeet() + 100.0f * toGoal, 9999999.9f );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::EntityText( GetBot()->GetEntity()->entindex(), 0, "Dismount top", 0.1f, 255, 255, 255, 255 );
NDebugOverlay::HorzArrow( GetFeet(), m_ladderDismountGoal->GetCenter(), 5.0f, 255, 255, 0, 255, true, 0.1f );
}
// test 2D vector here in case nav area is under the geometry a bit
const float tolerance = 10.0f;
if ( GetBot()->GetEntity()->GetLastKnownArea() == m_ladderDismountGoal && range < tolerance )
{
// reached dismount goal
m_ladderInfo = NULL;
return NO_LADDER;
}
return DISMOUNTING_LADDER_TOP;
}
//-----------------------------------------------------------------------------------------------------
PlayerLocomotion::LadderState PlayerLocomotion::DismountLadderBottom( void )
{
if ( m_ladderInfo == NULL || m_ladderTimer.IsElapsed() )
{
m_ladderInfo = NULL;
return NO_LADDER;
}
if ( GetBot()->GetEntity()->GetMoveType() == MOVETYPE_LADDER )
{
// near the bottom - just let go
GetBot()->GetEntity()->SetMoveType( MOVETYPE_WALK );
m_ladderInfo = NULL;
}
return NO_LADDER;
}
//-----------------------------------------------------------------------------------------------------
/**
* Update internal state
*/
void PlayerLocomotion::Update( void )
{
if ( TraverseLadder() )
{
return BaseClass::Update();
}
if ( m_isJumpingAcrossGap || m_isClimbingUpToLedge )
{
// force a run
SetMinimumSpeedLimit( GetRunSpeed() );
Vector toLanding = m_landingGoal - GetFeet();
toLanding.z = 0.0f;
toLanding.NormalizeInPlace();
if ( m_hasLeftTheGround )
{
// face into the jump/climb
GetBot()->GetBodyInterface()->AimHeadTowards( GetBot()->GetEntity()->EyePosition() + 100.0 * toLanding, IBody::MANDATORY, 0.25f, NULL, "Facing impending jump/climb" );
if ( IsOnGround() )
{
// back on the ground - jump is complete
m_isClimbingUpToLedge = false;
m_isJumpingAcrossGap = false;
SetMinimumSpeedLimit( 0.0f );
}
}
else
{
// haven't left the ground yet - just starting the jump
if ( !IsClimbingOrJumping() )
{
Jump();
}
Vector vel = GetBot()->GetEntity()->GetAbsVelocity();
if ( m_isJumpingAcrossGap )
{
// cheat and max our velocity in case we were stopped at the edge of this gap
vel.x = GetRunSpeed() * toLanding.x;
vel.y = GetRunSpeed() * toLanding.y;
// leave vel.z unchanged
}
GetBot()->GetEntity()->SetAbsVelocity( vel );
if ( !IsOnGround() )
{
// jump has begun
m_hasLeftTheGround = true;
}
}
Approach( m_landingGoal );
}
BaseClass::Update();
}
//-----------------------------------------------------------------------------------------------------
void PlayerLocomotion::AdjustPosture( const Vector &moveGoal )
{
// This function has no effect if we're not standing or crouching
IBody *body = GetBot()->GetBodyInterface();
if ( !body->IsActualPosture( IBody::STAND ) && !body->IsActualPosture( IBody::CROUCH ) )
return;
// not all games have auto-crouch, so don't assume it here
BaseClass::AdjustPosture( moveGoal );
}
//-----------------------------------------------------------------------------------------------------
/**
* Build a user command to move this player towards the goal position
*/
void PlayerLocomotion::Approach( const Vector &pos, float goalWeight )
{
VPROF_BUDGET( "PlayerLocomotion::Approach", "NextBot" );
BaseClass::Approach( pos );
AdjustPosture( pos );
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::Line( GetFeet(), pos, 255, 255, 0, true, 0.1f );
}
INextBotPlayerInput *playerButtons = dynamic_cast< INextBotPlayerInput * >( GetBot() );
if ( !playerButtons )
{
DevMsg( "PlayerLocomotion::Approach: No INextBotPlayerInput\n " );
return;
}
Vector forward3D;
m_player->EyeVectors( &forward3D );
Vector2D forward( forward3D.x, forward3D.y );
forward.NormalizeInPlace();
Vector2D right( forward.y, -forward.x );
// compute unit vector to goal position
Vector2D to = ( pos - GetFeet() ).AsVector2D();
float goalDistance = to.NormalizeInPlace();
float ahead = to.Dot( forward );
float side = to.Dot( right );
#ifdef NEED_TO_INTEGRATE_MOTION_CONTROLLED_CODE_FROM_L4D_PLAYERS
// If we're climbing ledges, we need to stay crouched to prevent player movement code from messing
// with our origin.
CTerrorPlayer *player = ToTerrorPlayer(m_player);
if ( player && player->IsMotionControlledZ( player->GetMainActivity() ) )
{
playerButtons->PressCrouchButton();
return;
}
#endif
if ( m_player->IsOnLadder() && IsUsingLadder() && ( m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER ) )
{
// we are on a ladder and WANT to be on a ladder.
playerButtons->PressForwardButton();
// Stay in center of ladder. The gamemovement will autocenter us in most cases, but this is needed in case it doesn't.
if ( m_ladderInfo )
{
Vector posOnLadder;
CalcClosestPointOnLine( GetFeet(), m_ladderInfo->m_bottom, m_ladderInfo->m_top, posOnLadder );
Vector alongLadder = m_ladderInfo->m_top - m_ladderInfo->m_bottom;
alongLadder.NormalizeInPlace();
Vector rightLadder = CrossProduct( alongLadder, m_ladderInfo->GetNormal() );
Vector away = GetFeet() - posOnLadder;
// we only want error in plane of ladder
float error = DotProduct( away, rightLadder );
away.NormalizeInPlace();
const float tolerance = 5.0f + 0.25f * GetBot()->GetBodyInterface()->GetHullWidth();
if ( error > tolerance )
{
if ( DotProduct( away, rightLadder ) > 0.0f )
{
playerButtons->PressLeftButton();
}
else
{
playerButtons->PressRightButton();
}
}
}
}
else
{
const float epsilon = 0.25f;
if ( NextBotPlayerMoveDirect.GetBool() )
{
if ( goalDistance > epsilon )
{
playerButtons->SetButtonScale( ahead, side );
}
}
if ( ahead > epsilon )
{
playerButtons->PressForwardButton();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() + 50.0f * Vector( forward.x, forward.y, 0.0f ), 15.0f, 0, 255, 0, 255, true, 0.1f );
}
}
else if ( ahead < -epsilon )
{
playerButtons->PressBackwardButton();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() - 50.0f * Vector( forward.x, forward.y, 0.0f ), 15.0f, 255, 0, 0, 255, true, 0.1f );
}
}
if ( side <= -epsilon )
{
playerButtons->PressLeftButton();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() - 50.0f * Vector( right.x, right.y, 0.0f ), 15.0f, 255, 0, 255, 255, true, 0.1f );
}
}
else if ( side >= epsilon )
{
playerButtons->PressRightButton();
if ( GetBot()->IsDebugging( NEXTBOT_LOCOMOTION ) )
{
NDebugOverlay::HorzArrow( m_player->GetAbsOrigin(), m_player->GetAbsOrigin() + 50.0f * Vector( right.x, right.y, 0.0f ), 15.0f, 0, 255, 255, 255, true, 0.1f );
}
}
}
if ( !IsRunning() )
{
playerButtons->PressWalkButton();
}
}
//----------------------------------------------------------------------------------------------------
/**
* Move the bot to the precise given position immediately,
*/
void PlayerLocomotion::DriveTo( const Vector &pos )
{
BaseClass::DriveTo( pos );
Approach( pos );
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const
{
// don't jump unless we have to
const PathFollower *path = GetBot()->GetCurrentPath();
if ( path )
{
const float watchForClimbRange = 75.0f;
if ( !path->IsDiscontinuityAhead( GetBot(), Path::CLIMB_UP, watchForClimbRange ) )
{
// we are not planning on climbing
// always allow climbing over movable obstacles
if ( obstacle && !const_cast< CBaseEntity * >( obstacle )->IsWorld() )
{
IPhysicsObject *physics = obstacle->VPhysicsGetObject();
if ( physics && physics->IsMoveable() )
{
// movable physics object - climb over it
return true;
}
}
if ( !GetBot()->GetLocomotionInterface()->IsStuck() )
{
// we're not stuck - don't try to jump up yet
return false;
}
}
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle )
{
if ( !IsClimbPossible( GetBot(), obstacle ) )
{
return false;
}
Jump();
m_isClimbingUpToLedge = true;
m_landingGoal = landingGoal;
m_hasLeftTheGround = false;
return true;
}
//----------------------------------------------------------------------------------------------------
void PlayerLocomotion::JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward )
{
Jump();
// face forward
GetBot()->GetBodyInterface()->AimHeadTowards( landingGoal, IBody::MANDATORY, 1.0f, NULL, "Looking forward while jumping a gap" );
m_isJumpingAcrossGap = true;
m_landingGoal = landingGoal;
m_hasLeftTheGround = false;
}
//----------------------------------------------------------------------------------------------------
void PlayerLocomotion::Jump( void )
{
m_isJumping = true;
m_jumpTimer.Start( 0.5f );
INextBotPlayerInput *playerButtons = dynamic_cast< INextBotPlayerInput * >( GetBot() );
if ( playerButtons )
{
playerButtons->PressJumpButton();
}
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsClimbingOrJumping( void ) const
{
if ( !m_isJumping )
return false;
if ( m_jumpTimer.IsElapsed() && IsOnGround() )
{
m_isJumping = false;
return false;
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsClimbingUpToLedge( void ) const
{
return m_isClimbingUpToLedge;
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsJumpingAcrossGap( void ) const
{
return m_isJumpingAcrossGap;
}
//----------------------------------------------------------------------------------------------------
/**
* Return true if standing on something
*/
bool PlayerLocomotion::IsOnGround( void ) const
{
return (m_player->GetGroundEntity() != NULL);
}
//----------------------------------------------------------------------------------------------------
/**
* Return the current ground entity or NULL if not on the ground
*/
CBaseEntity *PlayerLocomotion::GetGround( void ) const
{
return m_player->GetGroundEntity();
}
//----------------------------------------------------------------------------------------------------
/**
* Surface normal of the ground we are in contact with
*/
const Vector &PlayerLocomotion::GetGroundNormal( void ) const
{
static Vector up( 0, 0, 1.0f );
return up;
// TODO: Integrate movehelper_server for this: return m_player->GetGroundNormal();
}
//----------------------------------------------------------------------------------------------------
/**
* Climb the given ladder to the top and dismount
*/
void PlayerLocomotion::ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
{
// look up and push forward
// Vector goal = GetBot()->GetPosition() + 100.0f * ( Vector( 0, 0, 1.0f ) - ladder->GetNormal() );
// Approach( goal );
// FaceTowards( goal );
m_ladderState = APPROACHING_ASCENDING_LADDER;
m_ladderInfo = ladder;
m_ladderDismountGoal = dismountGoal;
}
//----------------------------------------------------------------------------------------------------
/**
* Descend the given ladder to the bottom and dismount
*/
void PlayerLocomotion::DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal )
{
// look down and push forward
// Vector goal = GetBot()->GetPosition() + 100.0f * ( Vector( 0, 0, -1.0f ) - ladder->GetNormal() );
// Approach( goal );
// FaceTowards( goal );
m_ladderState = APPROACHING_DESCENDING_LADDER;
m_ladderInfo = ladder;
m_ladderDismountGoal = dismountGoal;
}
//----------------------------------------------------------------------------------------------------
bool PlayerLocomotion::IsUsingLadder( void ) const
{
return ( m_ladderState != NO_LADDER );
}
//----------------------------------------------------------------------------------------------------
/**
* Rotate body to face towards "target"
*/
void PlayerLocomotion::FaceTowards( const Vector &target )
{
// player body follows view direction
Vector look( target.x, target.y, GetBot()->GetEntity()->EyePosition().z );
GetBot()->GetBodyInterface()->AimHeadTowards( look, IBody::BORING, 0.1f, NULL, "Body facing" );
}
//-----------------------------------------------------------------------------------------------------
/**
* Return position of "feet" - point below centroid of bot at feet level
*/
const Vector &PlayerLocomotion::GetFeet( void ) const
{
return m_player->GetAbsOrigin();
}
//-----------------------------------------------------------------------------------------------------
/**
* Return current world space velocity
*/
const Vector &PlayerLocomotion::GetVelocity( void ) const
{
return m_player->GetAbsVelocity();
}
//-----------------------------------------------------------------------------------------------------
float PlayerLocomotion::GetRunSpeed( void ) const
{
return m_player->MaxSpeed();
}
//-----------------------------------------------------------------------------------------------------
float PlayerLocomotion::GetWalkSpeed( void ) const
{
return 0.5f * m_player->MaxSpeed();
}
@@ -0,0 +1,219 @@
// NextBotPlayerLocomotion.h
// Locomotor for CBasePlayer derived bots
// Author: Michael Booth, November 2005
// Copyright (c) 2005 Turtle Rock Studios, Inc. - All Rights Reserved
#ifndef _NEXT_BOT_PLAYER_LOCOMOTION_H_
#define _NEXT_BOT_PLAYER_LOCOMOTION_H_
#include "NextBot.h"
#include "NextBotLocomotionInterface.h"
#include "Path/NextBotPathFollow.h"
class CBasePlayer;
//--------------------------------------------------------------------------------------------------
/**
* Basic player locomotion implementation
*/
class PlayerLocomotion : public ILocomotion
{
public:
DECLARE_CLASS( PlayerLocomotion, ILocomotion );
PlayerLocomotion( INextBot *bot );
virtual ~PlayerLocomotion() { }
virtual void Reset( void ); // reset to initial state
virtual void Update( void ); // update internal state
virtual void Approach( const Vector &pos, float goalWeight = 1.0f ); // move directly towards the given position
virtual void DriveTo( const Vector &pos ); // Move the bot to the precise given position immediately,
//
// ILocomotion modifiers
//
virtual bool ClimbUpToLedge( const Vector &landingGoal, const Vector &landingForward, const CBaseEntity *obstacle ); // initiate a jump to an adjacent high ledge, return false if climb can't start
virtual void JumpAcrossGap( const Vector &landingGoal, const Vector &landingForward ); // initiate a jump across an empty volume of space to far side
virtual void Jump( void ); // initiate a simple undirected jump in the air
virtual bool IsClimbingOrJumping( void ) const; // is jumping in any form
virtual bool IsClimbingUpToLedge( void ) const; // is climbing up to a high ledge
virtual bool IsJumpingAcrossGap( void ) const; // is jumping across a gap to the far side
virtual void Run( void ); // set desired movement speed to running
virtual void Walk( void ); // set desired movement speed to walking
virtual void Stop( void ); // set desired movement speed to stopped
virtual bool IsRunning( void ) const;
virtual void SetDesiredSpeed( float speed ); // set desired speed for locomotor movement
virtual float GetDesiredSpeed( void ) const; // returns the current desired speed
virtual void SetMinimumSpeedLimit( float limit ); // speed cannot drop below this
virtual void SetMaximumSpeedLimit( float limit ); // speed cannot rise above this
virtual bool IsOnGround( void ) const; // return true if standing on something
virtual CBaseEntity *GetGround( void ) const; // return the current ground entity or NULL if not on the ground
virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with
virtual void ClimbLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // climb the given ladder to the top and dismount
virtual void DescendLadder( const CNavLadder *ladder, const CNavArea *dismountGoal ); // descend the given ladder to the bottom and dismount
virtual bool IsUsingLadder( void ) const;
virtual bool IsAscendingOrDescendingLadder( void ) const; // we are actually on the ladder right now, either climbing up or down
virtual bool IsAbleToAutoCenterOnLadder( void ) const;
virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target"
virtual void SetDesiredLean( const QAngle &lean ) { }
virtual const QAngle &GetDesiredLean( void ) const { static QAngle junk; return junk; }
//
// ILocomotion information
//
virtual const Vector &GetFeet( void ) const; // return position of "feet" - point below centroid of bot at feet level
virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up
virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump
virtual float GetDeathDropHeight( void ) const; // distance at which we will die if we fall
virtual float GetRunSpeed( void ) const; // get maximum running speed
virtual float GetWalkSpeed( void ) const; // get maximum walking speed
virtual float GetMaxAcceleration( void ) const; // return maximum acceleration of locomotor
virtual float GetMaxDeceleration( void ) const; // return maximum deceleration of locomotor
virtual const Vector &GetVelocity( void ) const; // return current world space velocity
protected:
virtual void AdjustPosture( const Vector &moveGoal );
private:
CBasePlayer *m_player; // the player we are locomoting
mutable bool m_isJumping;
CountdownTimer m_jumpTimer;
bool m_isClimbingUpToLedge;
bool m_isJumpingAcrossGap;
Vector m_landingGoal;
bool m_hasLeftTheGround;
float m_desiredSpeed;
float m_minSpeedLimit;
float m_maxSpeedLimit;
bool TraverseLadder( void ); // when climbing/descending a ladder
enum LadderState
{
NO_LADDER, // not using a ladder
APPROACHING_ASCENDING_LADDER,
APPROACHING_DESCENDING_LADDER,
ASCENDING_LADDER,
DESCENDING_LADDER,
DISMOUNTING_LADDER_TOP,
DISMOUNTING_LADDER_BOTTOM,
};
LadderState m_ladderState;
LadderState ApproachAscendingLadder( void );
LadderState ApproachDescendingLadder( void );
LadderState AscendLadder( void );
LadderState DescendLadder( void );
LadderState DismountLadderTop( void );
LadderState DismountLadderBottom( void );
const CNavLadder *m_ladderInfo;
const CNavArea *m_ladderDismountGoal;
CountdownTimer m_ladderTimer; // a "give up" timer if things go awry
bool IsClimbPossible( INextBot *me, const CBaseEntity *obstacle ) const;
};
inline float PlayerLocomotion::GetStepHeight( void ) const
{
return 18.0f;
}
inline float PlayerLocomotion::GetMaxJumpHeight( void ) const
{
return 57.0f;
}
inline float PlayerLocomotion::GetDeathDropHeight( void ) const
{
return 200.0f;
}
inline float PlayerLocomotion::GetMaxAcceleration( void ) const
{
return 100.0f;
}
inline float PlayerLocomotion::GetMaxDeceleration( void ) const
{
return 200.0f;
}
inline void PlayerLocomotion::Run( void )
{
m_desiredSpeed = GetRunSpeed();
}
inline void PlayerLocomotion::Walk( void )
{
m_desiredSpeed = GetWalkSpeed();
}
inline void PlayerLocomotion::Stop( void )
{
m_desiredSpeed = 0.0f;
}
inline bool PlayerLocomotion::IsRunning( void ) const
{
return true;
}
inline void PlayerLocomotion::SetDesiredSpeed( float speed )
{
m_desiredSpeed = speed;
}
inline float PlayerLocomotion::GetDesiredSpeed( void ) const
{
return clamp( m_desiredSpeed, m_minSpeedLimit, m_maxSpeedLimit );
}
inline void PlayerLocomotion::SetMinimumSpeedLimit( float limit )
{
m_minSpeedLimit = limit;
}
inline void PlayerLocomotion::SetMaximumSpeedLimit( float limit )
{
m_maxSpeedLimit = limit;
}
inline bool PlayerLocomotion::IsAbleToAutoCenterOnLadder( void ) const
{
return IsUsingLadder() && (m_ladderState == ASCENDING_LADDER || m_ladderState == DESCENDING_LADDER);
}
inline bool PlayerLocomotion::IsAscendingOrDescendingLadder( void ) const
{
switch( m_ladderState )
{
case ASCENDING_LADDER:
case DESCENDING_LADDER:
case DISMOUNTING_LADDER_TOP:
case DISMOUNTING_LADDER_BOTTOM:
return true;
}
return false;
}
#endif // _NEXT_BOT_PLAYER_LOCOMOTION_H_
+195
View File
@@ -0,0 +1,195 @@
// simple_bot.cpp
// A simple bot
// Michael Booth, February 2009
#include "cbase.h"
#include "simple_bot.h"
#include "nav_mesh.h"
//-----------------------------------------------------------------------------------------------------
// Command to add a Simple Bot where your crosshairs are aiming
//-----------------------------------------------------------------------------------------------------
CON_COMMAND_F( simple_bot_add, "Add a simple bot.", FCVAR_CHEAT )
{
CBasePlayer *player = UTIL_GetCommandClient();
if ( !player )
{
return;
}
Vector forward;
player->EyeVectors( &forward );
trace_t result;
UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE, player, COLLISION_GROUP_NONE, &result );
if ( !result.DidHit() )
{
return;
}
CSimpleBot *bot = static_cast< CSimpleBot * >( CreateEntityByName( "simple_bot" ) );
if ( bot )
{
Vector forward = player->GetAbsOrigin() - result.endpos;
forward.z = 0.0f;
forward.NormalizeInPlace();
QAngle angles;
VectorAngles( forward, angles );
bot->SetAbsAngles( angles );
bot->SetAbsOrigin( result.endpos + Vector( 0, 0, 10.0f ) );
DispatchSpawn( bot );
}
}
//-----------------------------------------------------------------------------------------------------
// The Simple Bot
//-----------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( simple_bot, CSimpleBot );
PRECACHE_REGISTER( simple_bot );
//-----------------------------------------------------------------------------------------------------
CSimpleBot::CSimpleBot()
{
ALLOCATE_INTENTION_INTERFACE( CSimpleBot );
m_locomotor = new NextBotGroundLocomotion( this );
}
//-----------------------------------------------------------------------------------------------------
CSimpleBot::~CSimpleBot()
{
DEALLOCATE_INTENTION_INTERFACE;
if ( m_locomotor )
delete m_locomotor;
}
//-----------------------------------------------------------------------------------------------------
void CSimpleBot::Precache()
{
BaseClass::Precache();
#ifndef DOTA_DLL
PrecacheModel( "models/humans/group01/female_01.mdl" );
#endif
}
//-----------------------------------------------------------------------------------------------------
void CSimpleBot::Spawn( void )
{
BaseClass::Spawn();
#ifndef DOTA_DLL
SetModel( "models/humans/group01/female_01.mdl" );
#endif
}
//---------------------------------------------------------------------------------------------
// The Simple Bot behaviors
//---------------------------------------------------------------------------------------------
/**
* For use with TheNavMesh->ForAllAreas()
* Find the Nth area in the sequence
*/
class SelectNthAreaFunctor
{
public:
SelectNthAreaFunctor( int count )
{
m_count = count;
m_area = NULL;
}
bool operator() ( CNavArea *area )
{
m_area = area;
return ( m_count-- > 0 );
}
int m_count;
CNavArea *m_area;
};
//---------------------------------------------------------------------------------------------
/**
* This action causes the bot to pick a random nav area in the mesh and move to it, then
* pick another, etc.
* Actions usually each have their own .cpp/.h file and are organized into folders since there
* are often many of them. For this example, we're keeping everything to a single .cpp/.h file.
*/
class CSimpleBotRoam : public Action< CSimpleBot >
{
public:
//----------------------------------------------------------------------------------
// OnStart is called once when the Action first becomes active
virtual ActionResult< CSimpleBot > OnStart( CSimpleBot *me, Action< CSimpleBot > *priorAction )
{
// smooth out the bot's path following by moving toward a point farther down the path
m_path.SetMinLookAheadDistance( 300.0f );
return Continue();
}
//----------------------------------------------------------------------------------
// Update is called repeatedly (usually once per server frame) while the Action is active
virtual ActionResult< CSimpleBot > Update( CSimpleBot *me, float interval )
{
if ( m_path.IsValid() && !m_timer.IsElapsed() )
{
// PathFollower::Update() moves the bot along the path using the bot's ILocomotion and IBody interfaces
m_path.Update( me );
}
else
{
SelectNthAreaFunctor pick( RandomInt( 0, TheNavMesh->GetNavAreaCount() - 1 ) );
TheNavMesh->ForAllAreas( pick );
if ( pick.m_area )
{
CSimpleBotPathCost cost( me );
m_path.Compute( me, pick.m_area->GetCenter(), cost );
}
// follow this path for a random duration (or until we reach the end)
m_timer.Start( RandomFloat( 5.0f, 10.0f ) );
}
return Continue();
}
//----------------------------------------------------------------------------------
// this is an event handler - many more are available (see declaration of Action< Actor > in NextBotBehavior.h)
virtual EventDesiredResult< CSimpleBot > OnStuck( CSimpleBot *me )
{
// we are stuck trying to follow the current path - invalidate it so a new one is chosen
m_path.Invalidate();
return TryContinue();
}
virtual const char *GetName( void ) const { return "Roam"; } // return name of this action
private:
PathFollower m_path;
CountdownTimer m_timer;
};
//---------------------------------------------------------------------------------------------
/**
* Instantiate the bot's Intention interface and start the initial Action (CSimpleBotRoam in this case)
*/
IMPLEMENT_INTENTION_INTERFACE( CSimpleBot, CSimpleBotRoam )
+115
View File
@@ -0,0 +1,115 @@
// simple_bot.h
// A mininal example of a NextBotCombatCharacter (ie: non-player) bot
// Michael Booth, February 2009
#ifndef SIMPLE_BOT_H
#define SIMPLE_BOT_H
#include "NextBot.h"
#include "NextBotBehavior.h"
#include "NextBotGroundLocomotion.h"
#include "Path/NextBotPathFollow.h"
//----------------------------------------------------------------------------
/**
* A Simple Bot
*/
class CSimpleBot : public NextBotCombatCharacter
{
public:
DECLARE_CLASS( CSimpleBot, NextBotCombatCharacter );
CSimpleBot();
virtual ~CSimpleBot();
virtual void Precache();
virtual void Spawn( void );
// INextBot
DECLARE_INTENTION_INTERFACE( CSimpleBot )
virtual NextBotGroundLocomotion *GetLocomotionInterface( void ) const { return m_locomotor; }
private:
NextBotGroundLocomotion *m_locomotor;
};
//--------------------------------------------------------------------------------------------------------------
/**
* Functor used with the A* algorithm of NavAreaBuildPath() to determine the "cost" of moving from one area to another.
* "Cost" is generally the weighted distance between the centers of the areas. If you want the bot
* to avoid an area/ladder/elevator, increase the cost. If you want to disallow an area/ladder/elevator, return -1.
*/
class CSimpleBotPathCost : public IPathCost
{
public:
CSimpleBotPathCost( CSimpleBot *me )
{
m_me = me;
}
// return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
{
if ( fromArea == NULL )
{
// first area in path, no cost
return 0.0f;
}
else
{
if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
{
// our locomotor says we can't move here
return -1.0f;
}
// compute distance traveled along path so far
float dist;
if ( ladder )
{
dist = ladder->m_length;
}
else if ( length > 0.0 )
{
// optimization to avoid recomputing length
dist = length;
}
else
{
dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
}
float cost = dist + fromArea->GetCostSoFar();
// check height change
float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
{
if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
{
// too high to reach
return -1.0f;
}
// jumping is slower than flat ground
const float jumpPenalty = 5.0f;
cost += jumpPenalty * dist;
}
else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
{
// too far to drop
return -1.0f;
}
return cost;
}
}
CSimpleBot *m_me;
};
#endif // SIMPLE_BOT_H
+529
View File
@@ -0,0 +1,529 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Used to fire events based on the orientation of a given entity.
//
// Looks at its target's anglular velocity every frame and fires outputs
// as the angular velocity passes a given threshold value.
//
//=============================================================================//
#include "cbase.h"
#include "entityinput.h"
#include "entityoutput.h"
#include "eventqueue.h"
#include "mathlib/mathlib.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
enum
{
AVELOCITY_SENSOR_NO_LAST_RESULT = -2
};
ConVar g_debug_angularsensor( "g_debug_angularsensor", "0", FCVAR_CHEAT );
class CPointAngularVelocitySensor : public CPointEntity
{
DECLARE_CLASS( CPointAngularVelocitySensor, CPointEntity );
public:
CPointAngularVelocitySensor();
void Activate(void);
void Spawn(void);
void Think(void);
private:
float SampleAngularVelocity(CBaseEntity *pEntity);
int CompareToThreshold(CBaseEntity *pEntity, float flThreshold, bool bFireVelocityOutput);
void FireCompareOutput(int nCompareResult, CBaseEntity *pActivator);
void DrawDebugLines( void );
// Input handlers
void InputTest( inputdata_t &inputdata );
void InputTestWithInterval( inputdata_t &inputdata );
EHANDLE m_hTargetEntity; // Entity whose angles are being monitored.
float m_flThreshold; // The threshold angular velocity that we are looking for.
int m_nLastCompareResult; // The comparison result from our last measurement, expressed as -1, 0, or 1
int m_nLastFireResult; // The last result for which we fire the output.
float m_flFireTime;
float m_flFireInterval;
float m_flLastAngVelocity;
QAngle m_lastOrientation;
Vector m_vecAxis;
bool m_bUseHelper;
// Outputs
COutputFloat m_AngularVelocity;
// Compare the target's angular velocity to the threshold velocity and fire the appropriate output.
// These outputs are filtered by m_flFireInterval to ignore excessive oscillations.
COutputEvent m_OnLessThan;
COutputEvent m_OnLessThanOrEqualTo;
COutputEvent m_OnGreaterThan;
COutputEvent m_OnGreaterThanOrEqualTo;
COutputEvent m_OnEqualTo;
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS(point_angularvelocitysensor, CPointAngularVelocitySensor);
BEGIN_DATADESC( CPointAngularVelocitySensor )
// Fields
DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE ),
DEFINE_KEYFIELD(m_flThreshold, FIELD_FLOAT, "threshold"),
DEFINE_FIELD(m_nLastCompareResult, FIELD_INTEGER),
DEFINE_FIELD( m_nLastFireResult, FIELD_INTEGER ),
DEFINE_FIELD( m_flFireTime, FIELD_TIME ),
DEFINE_KEYFIELD( m_flFireInterval, FIELD_FLOAT, "fireinterval" ),
DEFINE_FIELD( m_flLastAngVelocity, FIELD_FLOAT ),
DEFINE_FIELD( m_lastOrientation, FIELD_VECTOR ),
// Inputs
DEFINE_INPUTFUNC(FIELD_VOID, "Test", InputTest),
DEFINE_INPUTFUNC(FIELD_VOID, "TestWithInterval", InputTestWithInterval),
// Outputs
DEFINE_OUTPUT(m_OnLessThan, "OnLessThan"),
DEFINE_OUTPUT(m_OnLessThanOrEqualTo, "OnLessThanOrEqualTo"),
DEFINE_OUTPUT(m_OnGreaterThan, "OnGreaterThan"),
DEFINE_OUTPUT(m_OnGreaterThanOrEqualTo, "OnGreaterThanOrEqualTo"),
DEFINE_OUTPUT(m_OnEqualTo, "OnEqualTo"),
DEFINE_OUTPUT(m_AngularVelocity, "AngularVelocity"),
DEFINE_KEYFIELD( m_vecAxis, FIELD_VECTOR, "axis" ),
DEFINE_KEYFIELD( m_bUseHelper, FIELD_BOOLEAN, "usehelper" ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: constructor provides default values
//-----------------------------------------------------------------------------
CPointAngularVelocitySensor::CPointAngularVelocitySensor()
{
m_flFireInterval = 0.2f;
}
//-----------------------------------------------------------------------------
// Purpose: Called when spawning after parsing keyvalues.
//-----------------------------------------------------------------------------
void CPointAngularVelocitySensor::Spawn(void)
{
m_flThreshold = fabs(m_flThreshold);
m_nLastFireResult = AVELOCITY_SENSOR_NO_LAST_RESULT;
m_nLastCompareResult = AVELOCITY_SENSOR_NO_LAST_RESULT;
// m_flFireInterval = 0.2;
m_lastOrientation = vec3_angle;
}
//-----------------------------------------------------------------------------
// Purpose: Called after all entities in the map have spawned.
//-----------------------------------------------------------------------------
void CPointAngularVelocitySensor::Activate(void)
{
BaseClass::Activate();
m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target );
if (m_hTargetEntity)
{
SetNextThink( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Purpose: Draws magic lines...
//-----------------------------------------------------------------------------
void CPointAngularVelocitySensor::DrawDebugLines( void )
{
if ( m_hTargetEntity )
{
Vector vForward, vRight, vUp;
AngleVectors( m_hTargetEntity->GetAbsAngles(), &vForward, &vRight, &vUp );
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vForward * 64, 255, 0, 0, false, 0 );
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vRight * 64, 0, 255, 0, false, 0 );
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vUp * 64, 0, 0, 255, false, 0 );
}
if ( m_bUseHelper == true )
{
QAngle Angles;
Vector vAxisForward, vAxisRight, vAxisUp;
Vector vLine = m_vecAxis - GetAbsOrigin();
VectorNormalize( vLine );
VectorAngles( vLine, Angles );
AngleVectors( Angles, &vAxisForward, &vAxisRight, &vAxisUp );
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vAxisForward * 64, 255, 0, 0, false, 0 );
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vAxisRight * 64, 0, 255, 0, false, 0 );
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + vAxisUp * 64, 0, 0, 255, false, 0 );
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns the magnitude of the entity's angular velocity.
//-----------------------------------------------------------------------------
float CPointAngularVelocitySensor::SampleAngularVelocity(CBaseEntity *pEntity)
{
if (pEntity->GetMoveType() == MOVETYPE_VPHYSICS)
{
IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
if (pPhys != NULL)
{
Vector vecVelocity;
AngularImpulse vecAngVelocity;
pPhys->GetVelocity(&vecVelocity, &vecAngVelocity);
QAngle angles;
pPhys->GetPosition( NULL, &angles );
float dt = gpGlobals->curtime - GetLastThink();
if ( dt == 0 )
dt = 0.1;
// HACKHACK: We don't expect a real 'delta' orientation here, just enough of an error estimate to tell if this thing
// is trying to move, but failing.
QAngle delta = angles - m_lastOrientation;
if ( ( delta.Length() / dt ) < ( vecAngVelocity.Length() * 0.01 ) )
{
return 0.0f;
}
m_lastOrientation = angles;
if ( m_bUseHelper == false )
{
return vecAngVelocity.Length();
}
else
{
Vector vLine = m_vecAxis - GetAbsOrigin();
VectorNormalize( vLine );
Vector vecWorldAngVelocity;
pPhys->LocalToWorldVector( &vecWorldAngVelocity, vecAngVelocity );
float flDot = DotProduct( vecWorldAngVelocity, vLine );
return flDot;
}
}
}
else
{
QAngle vecAngVel = pEntity->GetLocalAngularVelocity();
float flMax = MAX(fabs(vecAngVel[PITCH]), fabs(vecAngVel[YAW]));
return MAX(flMax, fabs(vecAngVel[ROLL]));
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Compares the given entity's angular velocity to the threshold velocity.
// Input : pEntity - Entity whose angular velocity is being measured.
// flThreshold -
// Output : Returns -1 if less than, 0 if equal to, or 1 if greater than the threshold.
//-----------------------------------------------------------------------------
int CPointAngularVelocitySensor::CompareToThreshold(CBaseEntity *pEntity, float flThreshold, bool bFireVelocityOutput)
{
if (pEntity == NULL)
{
return 0;
}
float flAngVelocity = SampleAngularVelocity(pEntity);
if ( g_debug_angularsensor.GetBool() )
{
DrawDebugLines();
}
if (bFireVelocityOutput && (flAngVelocity != m_flLastAngVelocity))
{
m_AngularVelocity.Set(flAngVelocity, pEntity, this);
m_flLastAngVelocity = flAngVelocity;
}
if (flAngVelocity > flThreshold)
{
return 1;
}
if (flAngVelocity == flThreshold)
{
return 0;
}
return -1;
}
//-----------------------------------------------------------------------------
// Called every frame to sense the angular velocity of the target entity.
// Output is filtered by m_flFireInterval to ignore excessive oscillations.
//-----------------------------------------------------------------------------
void CPointAngularVelocitySensor::Think(void)
{
if (m_hTargetEntity != NULL)
{
//
// Check to see if the measure entity's angular velocity has been within
// tolerance of the threshold for the given period of time.
//
int nCompare = CompareToThreshold(m_hTargetEntity, m_flThreshold, true);
if (nCompare != m_nLastCompareResult)
{
// If we've oscillated back to where we last fired the output, don't
// fire the same output again.
if (nCompare == m_nLastFireResult)
{
m_flFireTime = 0;
}
else if (m_nLastCompareResult != AVELOCITY_SENSOR_NO_LAST_RESULT)
{
//
// The value has changed -- reset the timer. We'll fire the output if
// it stays at this value until the interval expires.
//
m_flFireTime = gpGlobals->curtime + m_flFireInterval;
}
m_nLastCompareResult = nCompare;
}
else if ((m_flFireTime != 0) && (gpGlobals->curtime >= m_flFireTime))
{
//
// The compare result has held steady long enough -- time to
// fire the output.
//
FireCompareOutput(nCompare, this);
m_nLastFireResult = nCompare;
m_flFireTime = 0;
}
SetNextThink( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Fires the output after the fire interval if the velocity is stable.
//-----------------------------------------------------------------------------
void CPointAngularVelocitySensor::InputTestWithInterval( inputdata_t &inputdata )
{
if (m_hTargetEntity != NULL)
{
m_flFireTime = gpGlobals->curtime + m_flFireInterval;
m_nLastFireResult = AVELOCITY_SENSOR_NO_LAST_RESULT;
m_nLastCompareResult = CompareToThreshold(m_hTargetEntity, m_flThreshold, true);
SetNextThink( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for forcing an instantaneous test of the condition.
//-----------------------------------------------------------------------------
void CPointAngularVelocitySensor::InputTest( inputdata_t &inputdata )
{
int nCompareResult = CompareToThreshold(m_hTargetEntity, m_flThreshold, false);
FireCompareOutput(nCompareResult, inputdata.pActivator);
}
//-----------------------------------------------------------------------------
// Purpose: Fires the appropriate output based on the given comparison result.
// Input : nCompareResult -
// pActivator -
//-----------------------------------------------------------------------------
void CPointAngularVelocitySensor::FireCompareOutput( int nCompareResult, CBaseEntity *pActivator )
{
if (nCompareResult == -1)
{
m_OnLessThan.FireOutput(pActivator, this);
m_OnLessThanOrEqualTo.FireOutput(pActivator, this);
}
else if (nCompareResult == 1)
{
m_OnGreaterThan.FireOutput(pActivator, this);
m_OnGreaterThanOrEqualTo.FireOutput(pActivator, this);
}
else
{
m_OnEqualTo.FireOutput(pActivator, this);
m_OnLessThanOrEqualTo.FireOutput(pActivator, this);
m_OnGreaterThanOrEqualTo.FireOutput(pActivator, this);
}
}
// ============================================================================
//
// Simple velocity sensor
//
// ============================================================================
class CPointVelocitySensor : public CPointEntity
{
DECLARE_CLASS( CPointVelocitySensor, CPointEntity );
public:
void Spawn();
void Activate( void );
void Think( void );
private:
void SampleVelocity( void );
EHANDLE m_hTargetEntity; // Entity whose angles are being monitored.
Vector m_vecAxis; // Axis along which to measure the speed.
bool m_bEnabled; // Whether we're measuring or not
// Outputs
float m_fPrevVelocity; // stores velocity from last frame, so we only write the output if it has changed
COutputFloat m_Velocity;
void InputEnable( inputdata_t &inputdata );
void InputDisable( inputdata_t &inputdata );
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( point_velocitysensor, CPointVelocitySensor );
BEGIN_DATADESC( CPointVelocitySensor )
// Fields
DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE ),
DEFINE_KEYFIELD( m_vecAxis, FIELD_VECTOR, "axis" ),
DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "enabled" ),
DEFINE_FIELD( m_fPrevVelocity, FIELD_FLOAT ),
// Outputs
DEFINE_OUTPUT( m_Velocity, "Velocity" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
END_DATADESC()
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CPointVelocitySensor::Spawn()
{
Vector vLine = m_vecAxis - GetAbsOrigin();
VectorNormalize( vLine );
m_vecAxis = vLine;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPointVelocitySensor::Activate( void )
{
BaseClass::Activate();
m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target );
if ( m_bEnabled && m_hTargetEntity )
{
SetNextThink( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPointVelocitySensor::InputEnable( inputdata_t &inputdata )
{
// Don't interrupt us if we're already enabled
if ( m_bEnabled )
return;
m_bEnabled = true;
if ( m_hTargetEntity )
{
SetNextThink( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPointVelocitySensor::InputDisable( inputdata_t &inputdata )
{
m_bEnabled = false;
}
//-----------------------------------------------------------------------------
// Purpose: Called every frame
//-----------------------------------------------------------------------------
void CPointVelocitySensor::Think( void )
{
if ( m_hTargetEntity != NULL && m_bEnabled )
{
SampleVelocity();
SetNextThink( gpGlobals->curtime );
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns the magnitude of the entity's angular velocity.
//-----------------------------------------------------------------------------
void CPointVelocitySensor::SampleVelocity( void )
{
if ( m_hTargetEntity == NULL )
return;
Vector vecVelocity;
if ( m_hTargetEntity->GetMoveType() == MOVETYPE_VPHYSICS )
{
IPhysicsObject *pPhys = m_hTargetEntity->VPhysicsGetObject();
if ( pPhys != NULL )
{
pPhys->GetVelocity( &vecVelocity, NULL );
}
}
else
{
vecVelocity = m_hTargetEntity->GetAbsVelocity();
}
/*
float flSpeed = VectorNormalize( vecVelocity );
float flDot = ( m_vecAxis != vec3_origin ) ? DotProduct( vecVelocity, m_vecAxis ) : 1.0f;
*/
// We want the component of the velocity vector in the direction of the axis, which since the
// axis is normalized is simply their dot product (eg V . A = |V|*|A|*cos(theta) )
m_fPrevVelocity = ( m_vecAxis != vec3_origin ) ? DotProduct( vecVelocity, m_vecAxis ) : 1.0f;
// if it's changed since the last frame, poke the output
if ( m_fPrevVelocity != m_Velocity.Get() )
{
m_Velocity.Set( m_fPrevVelocity, NULL, NULL );
}
}
+283
View File
@@ -0,0 +1,283 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose: Dissolve entity to be attached to target entity. Serves two purposes:
//
// 1) An entity that can be placed by a level designer and triggered
// to ignite a target entity.
//
// 2) An entity that can be created at runtime to ignite a target entity.
//
//===========================================================================//
#include "cbase.h"
#include "RagdollBoogie.h"
#include "physics_prop_ragdoll.h"
#include "effect_dispatch_data.h"
#include "te_effect_dispatch.h"
#include "IEffects.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Make electriciy every so often
//-----------------------------------------------------------------------------
static const char *s_pZapContext = "ZapContext";
//-----------------------------------------------------------------------------
// Save/load
//-----------------------------------------------------------------------------
BEGIN_DATADESC( CRagdollBoogie )
DEFINE_FIELD( m_flStartTime, FIELD_TIME ),
DEFINE_FIELD( m_flBoogieLength, FIELD_FLOAT ),
DEFINE_FIELD( m_flMagnitude, FIELD_FLOAT ),
// Think this should be handled by StartTouch/etc.
// DEFINE_FIELD( m_nSuppressionCount, FIELD_INTEGER ),
DEFINE_FUNCTION( BoogieThink ),
DEFINE_FUNCTION( ZapThink ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( env_ragdoll_boogie, CRagdollBoogie );
//-----------------------------------------------------------------------------
// Purpose: Creates a flame and attaches it to a target entity.
// Input : pTarget -
//-----------------------------------------------------------------------------
CRagdollBoogie *CRagdollBoogie::Create( CBaseEntity *pTarget, float flMagnitude,
float flStartTime, float flLengthTime, int nSpawnFlags )
{
CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pTarget );
if ( !pRagdoll )
return NULL;
CRagdollBoogie *pBoogie = (CRagdollBoogie *)CreateEntityByName( "env_ragdoll_boogie" );
if ( pBoogie == NULL )
return NULL;
pBoogie->AddSpawnFlags( nSpawnFlags );
pBoogie->AttachToEntity( pTarget );
pBoogie->SetBoogieTime( flStartTime, flLengthTime );
pBoogie->SetMagnitude( flMagnitude );
pBoogie->Spawn();
return pBoogie;
}
//-----------------------------------------------------------------------------
// Spawn
//-----------------------------------------------------------------------------
void CRagdollBoogie::Precache()
{
BaseClass::Precache();
PrecacheEffect( "TeslaHitboxes" );
#ifdef HL2_EPISODIC
PrecacheScriptSound( "RagdollBoogie.Zap" );
#endif
}
//-----------------------------------------------------------------------------
// Spawn
//-----------------------------------------------------------------------------
void CRagdollBoogie::Spawn()
{
Precache();
BaseClass::Spawn();
SetThink( &CRagdollBoogie::BoogieThink );
SetNextThink( gpGlobals->curtime + 0.01f );
if ( HasSpawnFlags( SF_RAGDOLL_BOOGIE_ELECTRICAL ) )
{
SetContextThink( &CRagdollBoogie::ZapThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), s_pZapContext );
}
}
//-----------------------------------------------------------------------------
// Zap!
//-----------------------------------------------------------------------------
void CRagdollBoogie::ZapThink()
{
if ( !GetMoveParent() )
return;
CBaseAnimating *pRagdoll = GetMoveParent()->GetBaseAnimating();
if ( !pRagdoll )
return;
// Make electricity on the client
CStudioHdr *pStudioHdr = pRagdoll->GetModelPtr( );
if (!pStudioHdr)
return;
mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pRagdoll->GetHitboxSet() );
if ( set->numhitboxes == 0 )
return;
if ( m_nSuppressionCount == 0 )
{
CEffectData data;
data.m_nEntIndex = GetMoveParent()->entindex();
data.m_flMagnitude = 4;
data.m_flScale = HasSpawnFlags(SF_RAGDOLL_BOOGIE_ELECTRICAL_NARROW_BEAM) ? 1.0f : 2.0f;
DispatchEffect( "TeslaHitboxes", data );
}
#ifdef HL2_EPISODIC
EmitSound( "RagdollBoogie.Zap" );
#endif
SetContextThink( &CRagdollBoogie::ZapThink, gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ), s_pZapContext );
}
//-----------------------------------------------------------------------------
// Suppression count
//-----------------------------------------------------------------------------
void CRagdollBoogie::IncrementSuppressionCount( CBaseEntity *pTarget )
{
// Look for other boogies on the ragdoll + kill them
for ( CBaseEntity *pChild = pTarget->FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() )
{
CRagdollBoogie *pBoogie = dynamic_cast<CRagdollBoogie*>(pChild);
if ( !pBoogie )
continue;
++pBoogie->m_nSuppressionCount;
}
}
void CRagdollBoogie::DecrementSuppressionCount( CBaseEntity *pTarget )
{
// Look for other boogies on the ragdoll + kill them
CBaseEntity *pNext;
for ( CBaseEntity *pChild = pTarget->FirstMoveChild(); pChild; pChild = pNext )
{
pNext = pChild->NextMovePeer();
CRagdollBoogie *pBoogie = dynamic_cast<CRagdollBoogie*>(pChild);
if ( !pBoogie )
continue;
if ( --pBoogie->m_nSuppressionCount <= 0 )
{
pBoogie->m_nSuppressionCount = 0;
float dt = gpGlobals->curtime - pBoogie->m_flStartTime;
if ( dt >= pBoogie->m_flBoogieLength )
{
PhysCallbackRemove( pBoogie->NetworkProp() );
}
}
}
}
//-----------------------------------------------------------------------------
// Attach to an entity
//-----------------------------------------------------------------------------
void CRagdollBoogie::AttachToEntity( CBaseEntity *pTarget )
{
m_nSuppressionCount = 0;
// Look for other boogies on the ragdoll + kill them
CBaseEntity *pNext;
for ( CBaseEntity *pChild = pTarget->FirstMoveChild(); pChild; pChild = pNext )
{
pNext = pChild->NextMovePeer();
CRagdollBoogie *pBoogie = dynamic_cast<CRagdollBoogie*>(pChild);
if ( !pBoogie )
continue;
m_nSuppressionCount = pBoogie->m_nSuppressionCount;
UTIL_Remove( pChild );
}
FollowEntity( pTarget );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : lifetime -
//-----------------------------------------------------------------------------
void CRagdollBoogie::SetBoogieTime( float flStartTime, float flLengthTime )
{
m_flStartTime = flStartTime;
m_flBoogieLength = flLengthTime;
}
//-----------------------------------------------------------------------------
// Purpose: Burn targets around us
//-----------------------------------------------------------------------------
void CRagdollBoogie::SetMagnitude( float flMagnitude )
{
m_flMagnitude = flMagnitude;
}
//-----------------------------------------------------------------------------
// Purpose: Burn targets around us
//-----------------------------------------------------------------------------
void CRagdollBoogie::BoogieThink( void )
{
CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( GetMoveParent() );
if ( !pRagdoll )
{
UTIL_Remove( this );
return;
}
float flMagnitude = m_flMagnitude;
if ( m_flBoogieLength != 0 )
{
float dt = gpGlobals->curtime - m_flStartTime;
if ( dt >= m_flBoogieLength )
{
// Don't remove while suppressed... this helps if we try to start another boogie
if ( m_nSuppressionCount == 0 )
{
UTIL_Remove( this );
}
SetThink( NULL );
return;
}
if ( dt < 0 )
{
SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.2f ) );
return;
}
flMagnitude = SimpleSplineRemapVal( dt, 0.0f, m_flBoogieLength, m_flMagnitude, 0.0f );
}
if ( m_nSuppressionCount == 0 )
{
ragdoll_t *pRagdollPhys = pRagdoll->GetRagdoll( );
for ( int j = 0; j < pRagdollPhys->listCount; ++j )
{
float flMass = pRagdollPhys->list[j].pObject->GetMass();
float flForce = m_flMagnitude * flMass;
Vector vecForce;
vecForce = RandomVector( -flForce, flForce );
pRagdollPhys->list[j].pObject->ApplyForceCenter( vecForce );
}
}
SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.2f ) );
}
+51
View File
@@ -0,0 +1,51 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#ifndef RAGDOLLBOOGIE_H
#define RAGDOLLBOOGIE_H
#ifdef _WIN32
#pragma once
#endif
//-----------------------------------------------------------------------------
// Set this spawnflag before calling Spawn to get electrical effects
//-----------------------------------------------------------------------------
#define SF_RAGDOLL_BOOGIE_ELECTRICAL 0x10000
#define SF_RAGDOLL_BOOGIE_ELECTRICAL_NARROW_BEAM 0x20000
//-----------------------------------------------------------------------------
// Makes ragdolls DANCE!
//-----------------------------------------------------------------------------
class CRagdollBoogie : public CBaseEntity
{
DECLARE_DATADESC();
DECLARE_CLASS( CRagdollBoogie, CBaseEntity );
public:
static CRagdollBoogie *Create( CBaseEntity *pTarget, float flMagnitude, float flStartTime, float flLengthTime = 0.0f, int nSpawnFlags = 0 );
static void IncrementSuppressionCount( CBaseEntity *pTarget );
static void DecrementSuppressionCount( CBaseEntity *pTarget );
virtual void Precache();
void Spawn();
private:
void AttachToEntity( CBaseEntity *pTarget );
void SetBoogieTime( float flStartTime, float flLengthTime );
void SetMagnitude( float flMagnitude );
void BoogieThink( void );
void ZapThink();
float m_flStartTime;
float m_flBoogieLength;
float m_flMagnitude;
int m_nSuppressionCount;
};
#endif // RAGDOLLBOOGIE_H
+309
View File
@@ -0,0 +1,309 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "ServerNetworkProperty.h"
#include "tier0/dbg.h"
#include "gameinterface.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern CTimedEventMgr g_NetworkPropertyEventMgr;
//-----------------------------------------------------------------------------
// Save/load
//-----------------------------------------------------------------------------
BEGIN_DATADESC_NO_BASE( CServerNetworkProperty )
// DEFINE_FIELD( m_pOuter, FIELD_CLASSPTR ),
// DEFINE_FIELD( m_pPev, FIELD_CLASSPTR ),
// DEFINE_FIELD( m_PVSInfo, PVSInfo_t ),
// DEFINE_FIELD( m_pServerClass, FIELD_CLASSPTR ),
DEFINE_GLOBAL_FIELD( m_hParent, FIELD_EHANDLE ),
// DEFINE_FIELD( m_TimerEvent, CEventRegister ),
// DEFINE_FIELD( m_bPendingStateChange, FIELD_BOOLEAN ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CServerNetworkProperty::CServerNetworkProperty()
{
Init( NULL );
}
CServerNetworkProperty::~CServerNetworkProperty()
{
/* Free our transmit proxy.
if ( m_pTransmitProxy )
{
m_pTransmitProxy->Release();
}*/
engine->CleanUpEntityClusterList( &m_PVSInfo );
// remove the attached edict if it exists
DetachEdict();
}
//-----------------------------------------------------------------------------
// Initialization
//-----------------------------------------------------------------------------
void CServerNetworkProperty::Init( CBaseEntity *pEntity )
{
// NOTE: We're in pEntity's constructor so we can't call virtual methods of pEntity here
m_pPev = NULL;
m_pOuter = pEntity;
m_pServerClass = NULL;
// m_pTransmitProxy = NULL;
m_bPendingStateChange = false;
m_PVSInfo.m_nClusterCount = 0;
m_TimerEvent.Init( &g_NetworkPropertyEventMgr, this );
}
void CServerNetworkProperty::CacheServerClass()
{
m_pServerClass = m_pOuter->GetServerClass();
}
//-----------------------------------------------------------------------------
// Connects, disconnects edicts
//-----------------------------------------------------------------------------
void CServerNetworkProperty::AttachEdict( edict_t *pRequiredEdict )
{
Assert ( !m_pPev );
// see if there is an edict allocated for it, otherwise get one from the engine
if ( !pRequiredEdict )
{
pRequiredEdict = engine->CreateEdict();
}
m_pPev = pRequiredEdict;
m_pPev->SetEdict( GetBaseEntity(), true );
}
void CServerNetworkProperty::DetachEdict()
{
if ( m_pPev )
{
m_pPev->SetEdict( NULL, false );
engine->RemoveEdict( m_pPev );
m_pPev = NULL;
}
}
//-----------------------------------------------------------------------------
// Entity handles
//-----------------------------------------------------------------------------
IHandleEntity *CServerNetworkProperty::GetEntityHandle( )
{
return m_pOuter;
}
void CServerNetworkProperty::Release()
{
delete m_pOuter;
}
//-----------------------------------------------------------------------------
// Returns the network parent
//-----------------------------------------------------------------------------
CServerNetworkProperty* CServerNetworkProperty::GetNetworkParent()
{
CBaseEntity *pParent = m_hParent.Get();
return pParent ? pParent->NetworkProp() : NULL;
}
//-----------------------------------------------------------------------------
// Marks for deletion
//-----------------------------------------------------------------------------
void CServerNetworkProperty::MarkForDeletion()
{
m_pOuter->AddEFlags( EFL_KILLME );
}
bool CServerNetworkProperty::IsMarkedForDeletion() const
{
return ( m_pOuter->GetEFlags() & EFL_KILLME ) != 0;
}
//-----------------------------------------------------------------------------
// PVS information
//-----------------------------------------------------------------------------
void CServerNetworkProperty::RecomputePVSInformation()
{
if ( m_pPev && ( ( m_pPev->m_fStateFlags & FL_EDICT_DIRTY_PVS_INFORMATION ) != 0 ) )
{
m_pPev->m_fStateFlags &= ~FL_EDICT_DIRTY_PVS_INFORMATION;
engine->BuildEntityClusterList( edict(), &m_PVSInfo );
}
}
//-----------------------------------------------------------------------------
// Serverclass
//-----------------------------------------------------------------------------
ServerClass* CServerNetworkProperty::GetServerClass()
{
return m_pServerClass;
}
const char* CServerNetworkProperty::GetClassName() const
{
return STRING(m_pOuter->m_iClassname);
}
//-----------------------------------------------------------------------------
// Transmit proxies
/*-----------------------------------------------------------------------------
void CServerNetworkProperty::SetTransmitProxy( CBaseTransmitProxy *pProxy )
{
if ( m_pTransmitProxy )
{
m_pTransmitProxy->Release();
}
m_pTransmitProxy = pProxy;
if ( m_pTransmitProxy )
{
m_pTransmitProxy->AddRef();
}
}*/
//-----------------------------------------------------------------------------
// PVS rules
//-----------------------------------------------------------------------------
bool CServerNetworkProperty::IsInPVS( const edict_t *pRecipient, const void *pvs, int pvssize )
{
RecomputePVSInformation();
// ignore if not touching a PV leaf
// negative leaf count is a node number
// If no pvs, add any entity
Assert( pvs && ( edict() != pRecipient ) );
unsigned char *pPVS = ( unsigned char * )pvs;
if ( m_PVSInfo.m_nClusterCount < 0 ) // too many clusters, use headnode
{
return ( engine->CheckHeadnodeVisible( m_PVSInfo.m_nHeadNode, pPVS, pvssize ) != 0);
}
for ( int i = 0; i < m_PVSInfo.m_nClusterCount; i++ )
{
if (pPVS[m_PVSInfo.m_pClusters[i] >> 3] & (1 << (m_PVSInfo.m_pClusters[i] & 7) ))
return true;
}
return false; // not visible
}
//-----------------------------------------------------------------------------
// PVS: this function is called a lot, so it avoids function calls
//-----------------------------------------------------------------------------
bool CServerNetworkProperty::IsInPVS( const CCheckTransmitInfo *pInfo )
{
// PVS data must be up to date
Assert( !m_pPev || ( ( m_pPev->m_fStateFlags & FL_EDICT_DIRTY_PVS_INFORMATION ) == 0 ) );
int i;
// Early out if the areas are connected
if ( !m_PVSInfo.m_nAreaNum2 )
{
for ( i=0; i< pInfo->m_AreasNetworked; i++ )
{
int clientArea = pInfo->m_Areas[i];
if ( clientArea == m_PVSInfo.m_nAreaNum || engine->CheckAreasConnected( clientArea, m_PVSInfo.m_nAreaNum ) )
break;
}
}
else
{
// doors can legally straddle two areas, so
// we may need to check another one
for ( i=0; i< pInfo->m_AreasNetworked; i++ )
{
int clientArea = pInfo->m_Areas[i];
if ( clientArea == m_PVSInfo.m_nAreaNum || clientArea == m_PVSInfo.m_nAreaNum2 )
break;
if ( engine->CheckAreasConnected( clientArea, m_PVSInfo.m_nAreaNum ) )
break;
if ( engine->CheckAreasConnected( clientArea, m_PVSInfo.m_nAreaNum2 ) )
break;
}
}
if ( i == pInfo->m_AreasNetworked )
{
// areas not connected
return false;
}
// ignore if not touching a PV leaf
// negative leaf count is a node number
// If no pvs, add any entity
Assert( edict() != pInfo->m_pClientEnt );
unsigned char *pPVS = ( unsigned char * )pInfo->m_PVS;
if ( m_PVSInfo.m_nClusterCount < 0 ) // too many clusters, use headnode
{
return (engine->CheckHeadnodeVisible( m_PVSInfo.m_nHeadNode, pPVS, pInfo->m_nPVSSize ) != 0);
}
for ( i = m_PVSInfo.m_nClusterCount; --i >= 0; )
{
int nCluster = m_PVSInfo.m_pClusters[i];
if ( ((int)(pPVS[nCluster >> 3])) & BitVec_BitInByte( nCluster ) )
return true;
}
return false; // not visible
}
void CServerNetworkProperty::SetUpdateInterval( float val )
{
if ( val == 0 )
m_TimerEvent.StopUpdates();
else
m_TimerEvent.SetUpdateInterval( val );
}
void CServerNetworkProperty::FireEvent()
{
// Our timer went off. If our state has changed in the background, then
// trigger a state change in the edict.
if ( m_bPendingStateChange )
{
m_pPev->StateChanged();
m_bPendingStateChange = false;
}
}
+259
View File
@@ -0,0 +1,259 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#ifndef SERVERNETWORKPROPERTY_H
#define SERVERNETWORKPROPERTY_H
#ifdef _WIN32
#pragma once
#endif
#include "iservernetworkable.h"
#include "server_class.h"
#include "edict.h"
#include "timedeventmgr.h"
//
// Lightweight base class for networkable data on the server.
//
class CServerNetworkProperty : public IServerNetworkable, public IEventRegisterCallback
{
public:
DECLARE_CLASS_NOBASE( CServerNetworkProperty );
DECLARE_DATADESC();
public:
CServerNetworkProperty();
virtual ~CServerNetworkProperty();
public:
// IServerNetworkable implementation.
virtual IHandleEntity *GetEntityHandle( );
virtual edict_t *GetEdict() const;
virtual CBaseNetworkable* GetBaseNetworkable();
virtual CBaseEntity* GetBaseEntity();
virtual ServerClass* GetServerClass();
virtual const char* GetClassName() const;
virtual void Release();
virtual int AreaNum() const;
virtual PVSInfo_t* GetPVSInfo();
public:
// Other public methods
void Init( CBaseEntity *pEntity );
void CacheServerClass();
void AttachEdict( edict_t *pRequiredEdict = NULL );
// Methods to get the entindex + edict
int entindex() const;
edict_t *edict();
const edict_t *edict() const;
// Sets the edict pointer (for swapping edicts)
void SetEdict( edict_t *pEdict );
// All these functions call through to CNetStateMgr.
// See CNetStateMgr for details about these functions.
void NetworkStateForceUpdate();
void NetworkStateChanged();
void NetworkStateChanged( unsigned short offset );
// Marks the PVS information dirty
void MarkPVSInformationDirty();
// Marks for deletion
void MarkForDeletion();
bool IsMarkedForDeletion() const;
// Sets the network parent
void SetNetworkParent( EHANDLE hParent );
CServerNetworkProperty* GetNetworkParent();
// This is useful for entities that don't change frequently or that the client
// doesn't need updates on very often. If you use this mode, the server will only try to
// detect state changes every N seconds, so it will save CPU cycles and bandwidth.
//
// Note: N must be less than AUTOUPDATE_MAX_TIME_LENGTH.
//
// Set back to zero to disable the feature.
//
// This feature works on top of manual mode.
// - If you turn it on and manual mode is off, it will autodetect changes every N seconds.
// - If you turn it on and manual mode is on, then every N seconds it will only say there
// is a change if you've called NetworkStateChanged.
void SetUpdateInterval( float N );
// You can use this to override any entity's ShouldTransmit behavior.
// void SetTransmitProxy( CBaseTransmitProxy *pProxy );
// This version does a PVS check which also checks for connected areas
bool IsInPVS( const CCheckTransmitInfo *pInfo );
// This version doesn't do the area check
bool IsInPVS( const edict_t *pRecipient, const void *pvs, int pvssize );
// Called by the timed event manager when it's time to detect a state change.
virtual void FireEvent();
// Recomputes PVS information
void RecomputePVSInformation();
private:
// Detaches the edict.. should only be called by CBaseNetworkable's destructor.
void DetachEdict();
CBaseEntity *GetOuter();
// Marks the networkable that it will should transmit
void SetTransmit( CCheckTransmitInfo *pInfo );
private:
CBaseEntity *m_pOuter;
// CBaseTransmitProxy *m_pTransmitProxy;
edict_t *m_pPev;
PVSInfo_t m_PVSInfo;
ServerClass *m_pServerClass;
// NOTE: This state is 'owned' by the entity. It's only copied here
// also to help improve cache performance in networking code.
EHANDLE m_hParent;
// Counters for SetUpdateInterval.
CEventRegister m_TimerEvent;
bool m_bPendingStateChange : 1;
// friend class CBaseTransmitProxy;
};
//-----------------------------------------------------------------------------
// inline methods // TODOMO does inline work on virtual functions ?
//-----------------------------------------------------------------------------
inline CBaseNetworkable* CServerNetworkProperty::GetBaseNetworkable()
{
return NULL;
}
inline CBaseEntity* CServerNetworkProperty::GetBaseEntity()
{
return m_pOuter;
}
inline CBaseEntity *CServerNetworkProperty::GetOuter()
{
return m_pOuter;
}
inline PVSInfo_t *CServerNetworkProperty::GetPVSInfo()
{
return &m_PVSInfo;
}
//-----------------------------------------------------------------------------
// Marks the PVS information dirty
//-----------------------------------------------------------------------------
inline void CServerNetworkProperty::MarkPVSInformationDirty()
{
if ( m_pPev )
{
m_pPev->m_fStateFlags |= FL_EDICT_DIRTY_PVS_INFORMATION;
}
}
//-----------------------------------------------------------------------------
// Sets/gets the network parent
//-----------------------------------------------------------------------------
inline void CServerNetworkProperty::SetNetworkParent( EHANDLE hParent )
{
m_hParent = hParent;
}
//-----------------------------------------------------------------------------
// Methods related to the net state mgr
//-----------------------------------------------------------------------------
inline void CServerNetworkProperty::NetworkStateForceUpdate()
{
if ( m_pPev )
m_pPev->StateChanged();
}
inline void CServerNetworkProperty::NetworkStateChanged()
{
// If we're using the timer, then ignore this call.
if ( m_TimerEvent.IsRegistered() )
{
// If we're waiting for a timer event, then queue the change so it happens
// when the timer goes off.
m_bPendingStateChange = true;
}
else
{
if ( m_pPev )
m_pPev->StateChanged();
}
}
inline void CServerNetworkProperty::NetworkStateChanged( unsigned short varOffset )
{
// If we're using the timer, then ignore this call.
if ( m_TimerEvent.IsRegistered() )
{
// If we're waiting for a timer event, then queue the change so it happens
// when the timer goes off.
m_bPendingStateChange = true;
}
else
{
if ( m_pPev )
m_pPev->StateChanged( varOffset );
}
}
//-----------------------------------------------------------------------------
// Methods to get the entindex + edict
//-----------------------------------------------------------------------------
inline int CServerNetworkProperty::entindex() const
{
return ENTINDEX( m_pPev );
}
inline edict_t* CServerNetworkProperty::GetEdict() const
{
// This one's virtual, that's why we have to two other versions
return m_pPev;
}
inline edict_t *CServerNetworkProperty::edict()
{
return m_pPev;
}
inline const edict_t *CServerNetworkProperty::edict() const
{
return m_pPev;
}
//-----------------------------------------------------------------------------
// Sets the edict pointer (for swapping edicts)
//-----------------------------------------------------------------------------
inline void CServerNetworkProperty::SetEdict( edict_t *pEdict )
{
m_pPev = pEdict;
}
inline int CServerNetworkProperty::AreaNum() const
{
const_cast<CServerNetworkProperty*>(this)->RecomputePVSInformation();
return m_PVSInfo.m_nAreaNum;
}
#endif // SERVERNETWORKPROPERTY_H
+184
View File
@@ -0,0 +1,184 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "igamesystem.h"
#include "entitylist.h"
#include "SkyCamera.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// automatically hooks in the system's callbacks
CEntityClassList<CSkyCamera> g_SkyList;
template <> CSkyCamera *CEntityClassList<CSkyCamera>::m_pClassList = NULL;
CHandle<CSkyCamera> g_hActiveSkybox = INVALID_EHANDLE;
#ifdef PORTAL2
//------------------------------------------------------------------------------
// Purpose: NPC step trough AI
//------------------------------------------------------------------------------
void CC_SkyboxSwap( void )
{
// Make this cyclical for now!
if ( g_SkyList.m_pClassList->m_pNext )
{
g_SkyList.m_pClassList->m_pNext->m_pNext = g_SkyList.m_pClassList;
}
g_SkyList.m_pClassList = g_SkyList.m_pClassList->m_pNext;
}
static ConCommand skybox_swap("skybox_swap", CC_SkyboxSwap, "Swap through the skyboxes in our queue", FCVAR_CHEAT );
#endif // PORTAL2
//-----------------------------------------------------------------------------
// Retrives the current skycamera
//-----------------------------------------------------------------------------
CSkyCamera* GetCurrentSkyCamera()
{
if (g_hActiveSkybox.Get() == NULL)
{
g_hActiveSkybox = GetSkyCameraList();
}
return g_hActiveSkybox.Get();
}
CSkyCamera* GetSkyCameraList()
{
return g_SkyList.m_pClassList;
}
//=============================================================================
LINK_ENTITY_TO_CLASS( sky_camera, CSkyCamera );
BEGIN_DATADESC( CSkyCamera )
DEFINE_KEYFIELD( m_skyboxData.scale, FIELD_INTEGER, "scale" ),
DEFINE_FIELD( m_skyboxData.origin, FIELD_VECTOR ),
DEFINE_FIELD( m_skyboxData.area, FIELD_INTEGER ),
// Quiet down classcheck
// DEFINE_FIELD( m_skyboxData, sky3dparams_t ),
// This is re-set up in the constructor
// DEFINE_FIELD( m_pNext, CSkyCamera ),
// fog data for 3d skybox
DEFINE_KEYFIELD( m_bUseAngles, FIELD_BOOLEAN, "use_angles" ),
DEFINE_KEYFIELD( m_skyboxData.fog.enable, FIELD_BOOLEAN, "fogenable" ),
DEFINE_KEYFIELD( m_skyboxData.fog.blend, FIELD_BOOLEAN, "fogblend" ),
DEFINE_KEYFIELD( m_skyboxData.fog.dirPrimary, FIELD_VECTOR, "fogdir" ),
DEFINE_KEYFIELD( m_skyboxData.fog.colorPrimary, FIELD_COLOR32, "fogcolor" ),
DEFINE_KEYFIELD( m_skyboxData.fog.colorSecondary, FIELD_COLOR32, "fogcolor2" ),
DEFINE_KEYFIELD( m_skyboxData.fog.start, FIELD_FLOAT, "fogstart" ),
DEFINE_KEYFIELD( m_skyboxData.fog.end, FIELD_FLOAT, "fogend" ),
DEFINE_KEYFIELD( m_skyboxData.fog.maxdensity, FIELD_FLOAT, "fogmaxdensity" ),
DEFINE_KEYFIELD( m_skyboxData.fog.HDRColorScale, FIELD_FLOAT, "HDRColorScale" ),
DEFINE_INPUTFUNC( FIELD_VOID, "ActivateSkybox", InputActivateSkybox ),
END_DATADESC()
//-----------------------------------------------------------------------------
// List of maps in HL2 that we must apply our skybox fog fixup hack to
//-----------------------------------------------------------------------------
static const char *s_pBogusFogMaps[] =
{
"d1_canals_01",
"d1_canals_01a",
"d1_canals_02",
"d1_canals_03",
"d1_canals_09",
"d1_canals_10",
"d1_canals_11",
"d1_canals_12",
"d1_canals_13",
"d1_eli_01",
"d1_trainstation_01",
"d1_trainstation_03",
"d1_trainstation_04",
"d1_trainstation_05",
"d1_trainstation_06",
"d3_c17_04",
"d3_c17_11",
"d3_c17_12",
"d3_citadel_01",
NULL
};
//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CSkyCamera::CSkyCamera()
{
g_SkyList.Insert( this );
m_skyboxData.fog.maxdensity = 1.0f;
m_skyboxData.fog.HDRColorScale = 1.0f;
}
CSkyCamera::~CSkyCamera()
{
g_SkyList.Remove( this );
}
void CSkyCamera::Spawn( void )
{
m_skyboxData.origin = GetLocalOrigin();
m_skyboxData.area = engine->GetArea( m_skyboxData.origin );
Precache();
}
//-----------------------------------------------------------------------------
// Activate!
//-----------------------------------------------------------------------------
void CSkyCamera::Activate( )
{
BaseClass::Activate();
if ( m_bUseAngles )
{
AngleVectors( GetAbsAngles(), &m_skyboxData.fog.dirPrimary.GetForModify() );
m_skyboxData.fog.dirPrimary.GetForModify() *= -1.0f;
}
#ifdef HL2_DLL
// NOTE! This is a hack. There was a bug in the skybox fog computation
// on the client DLL that caused it to use the average of the primary and
// secondary fog color when blending was enabled. The bug is fixed, but to make
// the maps look the same as before the bug fix without having to download new maps,
// I have to cheat here and slam the primary and secondary colors to be the average of
// the primary and secondary colors.
if ( m_skyboxData.fog.blend )
{
for ( int i = 0; s_pBogusFogMaps[i]; ++i )
{
if ( !Q_stricmp( s_pBogusFogMaps[i], STRING(gpGlobals->mapname) ) )
{
m_skyboxData.fog.colorPrimary.SetR( ( m_skyboxData.fog.colorPrimary.GetR() + m_skyboxData.fog.colorSecondary.GetR() ) * 0.5f );
m_skyboxData.fog.colorPrimary.SetG( ( m_skyboxData.fog.colorPrimary.GetG() + m_skyboxData.fog.colorSecondary.GetG() ) * 0.5f );
m_skyboxData.fog.colorPrimary.SetB( ( m_skyboxData.fog.colorPrimary.GetB() + m_skyboxData.fog.colorSecondary.GetB() ) * 0.5f );
m_skyboxData.fog.colorPrimary.SetA( ( m_skyboxData.fog.colorPrimary.GetA() + m_skyboxData.fog.colorSecondary.GetA() ) * 0.5f );
m_skyboxData.fog.colorSecondary = m_skyboxData.fog.colorPrimary;
}
}
}
#endif
}
//-----------------------------------------------------------------------------
// Activate!
//-----------------------------------------------------------------------------
void CSkyCamera::InputActivateSkybox( inputdata_t &inputdata )
{
g_hActiveSkybox = this;
}
+49
View File
@@ -0,0 +1,49 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Resource collection entity
//
// $NoKeywords: $
//=============================================================================//
#ifndef SKYCAMERA_H
#define SKYCAMERA_H
#ifdef _WIN32
#pragma once
#endif
class CSkyCamera;
//=============================================================================
//
// Sky Camera Class
//
class CSkyCamera : public CLogicalEntity
{
DECLARE_CLASS( CSkyCamera, CLogicalEntity );
public:
DECLARE_DATADESC();
CSkyCamera();
~CSkyCamera();
virtual void Spawn( void );
virtual void Activate();
void InputActivateSkybox( inputdata_t &inputdata );
public:
sky3dparams_t m_skyboxData;
bool m_bUseAngles;
CSkyCamera *m_pNext;
};
//-----------------------------------------------------------------------------
// Retrives the current skycamera
//-----------------------------------------------------------------------------
CSkyCamera* GetCurrentSkyCamera();
CSkyCamera* GetSkyCameraList();
#endif // SKYCAMERA_H
+539
View File
@@ -0,0 +1,539 @@
//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Template entities are used by spawners to create copies of entities
// that were configured by the level designer. This allows us to spawn
// entities with arbitrary sets of key/value data and entity I/O
// connections.
//
// Template entities are marked with a special spawnflag which causes
// them not to spawn, but to be saved as a string containing all the
// map data (keyvalues and I/O connections) from the BSP. Template
// entities are looked up by name by the spawner, which copies the
// map data into a local string (that's how the template data is saved
// and restored). Once all the entities in the map have been activated,
// the template database is freed.
//
//=============================================================================//
#include "cbase.h"
#include "igamesystem.h"
#include "mapentities_shared.h"
#include "point_template.h"
#include "eventqueue.h"
#include "TemplateEntities.h"
#include "utldict.h"
#include "entitydefs.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar template_debug( "template_debug", "0" );
// This is appended to key's values that will need to be unique in template instances
const char *ENTITYIO_FIXUP_STRING = "&0000";
int MapEntity_GetNumKeysInEntity( const char *pEntData );
struct TemplateEntityData_t
{
const char *pszName;
char *pszMapData;
string_t iszMapData;
int iMapDataLength;
bool bNeedsEntityIOFixup; // If true, this template has entity I/O in its mapdata that needs fixup before spawning.
char *pszFixedMapData; // A single copy of this template that we used to fix up the Entity I/O whenever someone wants a fixed version of this template
int m_nHammerID; // Used to update the template in Foundry
DECLARE_SIMPLE_DATADESC();
};
BEGIN_SIMPLE_DATADESC( TemplateEntityData_t )
//DEFINE_FIELD( pszName, FIELD_STRING ), // Saved custom, see below
//DEFINE_FIELD( pszMapData, FIELD_STRING ), // Saved custom, see below
DEFINE_FIELD( iszMapData, FIELD_STRING ),
DEFINE_FIELD( iMapDataLength, FIELD_INTEGER ),
DEFINE_FIELD( bNeedsEntityIOFixup, FIELD_BOOLEAN ),
//DEFINE_FIELD( pszFixedMapData, FIELD_STRING ), // Not saved at all
END_DATADESC()
struct grouptemplate_t
{
CEntityMapData *pMapDataParser;
char pszName[MAPKEY_MAXLENGTH];
int iIndex;
bool bChangeTargetname;
};
static CUtlVector<TemplateEntityData_t *> g_Templates;
int g_iCurrentTemplateInstance;
void Templates_FreeTemplate( TemplateEntityData_t *pTemplate )
{
free((void *)pTemplate->pszName);
free(pTemplate->pszMapData);
if ( pTemplate->pszFixedMapData )
{
free(pTemplate->pszFixedMapData);
}
free(pTemplate);
}
//-----------------------------------------------------------------------------
// Purpose: Saves the given entity's keyvalue data for later use by a spawner.
// Returns the index into the templates.
//-----------------------------------------------------------------------------
int Templates_Add(CBaseEntity *pEntity, const char *pszMapData, int nLen, int nHammerID)
{
const char *pszName = STRING(pEntity->GetEntityName());
if ((!pszName) || (!strlen(pszName)))
{
DevWarning(1, "RegisterTemplateEntity: template entity with no name, class %s\n", pEntity->GetClassname());
return -1;
}
TemplateEntityData_t *pEntData = (TemplateEntityData_t *)malloc(sizeof(TemplateEntityData_t));
pEntData->m_nHammerID = nHammerID;
pEntData->pszName = strdup( pszName );
// We may modify the values of the keys in this mapdata chunk later on to fix Entity I/O
// connections. For this reason, we need to ensure we have enough memory to do that.
int iKeys = MapEntity_GetNumKeysInEntity( pszMapData );
int iExtraSpace = (strlen(ENTITYIO_FIXUP_STRING)+1) * iKeys;
// Extra 1 because the mapdata passed in isn't null terminated
pEntData->iMapDataLength = nLen + iExtraSpace + 1;
pEntData->pszMapData = (char *)malloc( pEntData->iMapDataLength );
memcpy(pEntData->pszMapData, pszMapData, nLen + 1);
pEntData->pszMapData[nLen] = '\0';
// We don't alloc these suckers right now because that gives us no time to
// tweak them for Entity I/O purposes.
pEntData->iszMapData = NULL_STRING;
pEntData->bNeedsEntityIOFixup = false;
pEntData->pszFixedMapData = NULL;
return g_Templates.AddToTail(pEntData);
}
void Templates_RemoveByHammerID( int nHammerID )
{
for ( int i=g_Templates.Count()-1; i >= 0; i-- )
{
if ( g_Templates[i]->m_nHammerID == nHammerID )
{
Templates_FreeTemplate( g_Templates[i] );
g_Templates.Remove( i );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if the specified index needs to be fixed up to be unique
// when the template is spawned.
//-----------------------------------------------------------------------------
bool Templates_IndexRequiresEntityIOFixup( int iIndex )
{
Assert( iIndex < g_Templates.Count() );
return g_Templates[iIndex]->bNeedsEntityIOFixup;
}
//-----------------------------------------------------------------------------
// Purpose: Looks up a template entity by its index in the templates
// Used by point_templates because they often have multiple templates with the same name
//-----------------------------------------------------------------------------
string_t Templates_FindByIndex( int iIndex )
{
Assert( iIndex < g_Templates.Count() );
// First time through we alloc the mapdata onto the pool.
// It's safe to do it now because this isn't called until post Entity I/O cleanup.
if ( g_Templates[iIndex]->iszMapData == NULL_STRING )
{
g_Templates[iIndex]->iszMapData = AllocPooledString( g_Templates[iIndex]->pszMapData );
}
return g_Templates[iIndex]->iszMapData;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int Templates_GetStringSize( int iIndex )
{
Assert( iIndex < g_Templates.Count() );
return g_Templates[iIndex]->iMapDataLength;
}
//-----------------------------------------------------------------------------
// Purpose: Looks up a template entity by name, returning the map data blob as
// a null-terminated string containing key/value pairs.
// NOTE: This can't handle multiple templates with the same targetname.
//-----------------------------------------------------------------------------
string_t Templates_FindByTargetName(const char *pszName)
{
int nCount = g_Templates.Count();
for (int i = 0; i < nCount; i++)
{
TemplateEntityData_t *pTemplate = g_Templates.Element(i);
if ( !stricmp(pTemplate->pszName, pszName) )
return Templates_FindByIndex( i );
}
return NULL_STRING;
}
//-----------------------------------------------------------------------------
// Purpose: A CPointTemplate has asked us to reconnect all the entity I/O links
// inside it's templates. Go through the keys and add look for values
// that match a name within the group's entity names. Append %d to any
// found values, which will later be filled out by a unique identifier
// whenever the template is instanced.
//-----------------------------------------------------------------------------
void Templates_ReconnectIOForGroup( CPointTemplate *pGroup )
{
int iCount = pGroup->GetNumTemplates();
if ( !iCount )
return;
// First assemble a list of the targetnames of all the templates in the group.
// We need to store off the original names here, because we're going to change
// them as we go along.
CUtlVector< grouptemplate_t > GroupTemplates;
int i;
for ( i = 0; i < iCount; i++ )
{
grouptemplate_t newGroupTemplate;
newGroupTemplate.iIndex = pGroup->GetTemplateIndexForTemplate(i);
newGroupTemplate.pMapDataParser = new CEntityMapData( g_Templates[ newGroupTemplate.iIndex ]->pszMapData, g_Templates[ newGroupTemplate.iIndex ]->iMapDataLength );
Assert( newGroupTemplate.pMapDataParser );
newGroupTemplate.pMapDataParser->ExtractValue( "targetname", newGroupTemplate.pszName );
newGroupTemplate.bChangeTargetname = false;
GroupTemplates.AddToTail( newGroupTemplate );
}
if (pGroup->AllowNameFixup())
{
char keyName[MAPKEY_MAXLENGTH];
char value[MAPKEY_MAXLENGTH];
char valueclipped[MAPKEY_MAXLENGTH];
// Now go through all the entities in the group and parse their mapdata keyvalues.
// We're looking for any values that match targetnames of any of the group entities.
for ( i = 0; i < iCount; i++ )
{
// We need to know what instance of each key we're changing.
// Store a table of the count of the keys we've run into.
CUtlDict< int, int > KeyInstanceCount;
CEntityMapData *mapData = GroupTemplates[i].pMapDataParser;
// Loop through our keys
if ( !mapData->GetFirstKey(keyName, value) )
continue;
do
{
// Ignore targetnames
if ( !stricmp( keyName, "targetname" ) )
continue;
// Add to the count for this
int idx = KeyInstanceCount.Find( keyName );
if ( idx == KeyInstanceCount.InvalidIndex() )
{
idx = KeyInstanceCount.Insert( keyName, 0 );
}
KeyInstanceCount[idx]++;
// Entity I/O values are stored as "Targetname,<data>", so we need to see if there's a ',' in the string
char *sValue = value;
// FIXME: This is very brittle. Any key with a , will not be found.
char delimiter = VMF_IOPARAM_STRING_DELIMITER;
if( strchr( value, delimiter ) == NULL )
{
delimiter = ',';
}
char *s = strchr( value, delimiter );
if ( s )
{
// Grab just the targetname of the receiver
Q_strncpy( valueclipped, value, (s - value+1) );
sValue = valueclipped;
}
// Loop through our group templates
for ( int iTName = 0; iTName < iCount; iTName++ )
{
char *pName = GroupTemplates[iTName].pszName;
if ( stricmp( pName, sValue ) )
continue;
if ( template_debug.GetInt() )
{
Msg("Template Connection Found: Key %s (\"%s\") in entity named \"%s\"(%d) matches entity %d's targetname\n", keyName, sValue, GroupTemplates[i].pszName, i, iTName );
}
char newvalue[MAPKEY_MAXLENGTH];
// Get the current key instance. (-1 because it's this one we're changing)
int nKeyInstance = KeyInstanceCount[idx] - 1;
// Add our IO value to the targetname
// We need to append it if this isn't an Entity I/O value, or prepend it to the ',' if it is
if ( s )
{
Q_strncpy( newvalue, valueclipped, MAPKEY_MAXLENGTH );
Q_strncat( newvalue, ENTITYIO_FIXUP_STRING, sizeof(newvalue), COPY_ALL_CHARACTERS );
Q_strncat( newvalue, s, sizeof(newvalue), COPY_ALL_CHARACTERS );
mapData->SetValue( keyName, newvalue, nKeyInstance );
}
else
{
Q_strncpy( newvalue, sValue, MAPKEY_MAXLENGTH );
Q_strncat( newvalue, ENTITYIO_FIXUP_STRING, sizeof(newvalue), COPY_ALL_CHARACTERS );
mapData->SetValue( keyName, newvalue, nKeyInstance );
}
// Remember we changed this targetname
GroupTemplates[iTName].bChangeTargetname = true;
// Set both entity's flags telling them their template needs fixup when it's spawned
g_Templates[ GroupTemplates[i].iIndex ]->bNeedsEntityIOFixup = true;
g_Templates[ GroupTemplates[iTName].iIndex ]->bNeedsEntityIOFixup = true;
}
}
while ( mapData->GetNextKey(keyName, value) );
}
// Now change targetnames for all entities that need them changed
for ( i = 0; i < iCount; i++ )
{
char value[MAPKEY_MAXLENGTH];
if ( GroupTemplates[i].bChangeTargetname )
{
CEntityMapData *mapData = GroupTemplates[i].pMapDataParser;
mapData->ExtractValue( "targetname", value );
Q_strncat( value, ENTITYIO_FIXUP_STRING, sizeof(value), COPY_ALL_CHARACTERS );
mapData->SetValue( "targetname", value );
}
}
}
// Delete our group parsers
for ( i = 0; i < iCount; i++ )
{
delete GroupTemplates[i].pMapDataParser;
}
GroupTemplates.Purge();
}
//-----------------------------------------------------------------------------
// Purpose: Someone's about to start instancing a new group of entities.
// Generate a unique identifier for this group.
//-----------------------------------------------------------------------------
void Templates_StartUniqueInstance( void )
{
g_iCurrentTemplateInstance++;
// Make sure there's enough room to fit it into the string
int iMax = pow(10.0f, (int)((strlen(ENTITYIO_FIXUP_STRING) - 1))); // -1 for the &
if ( g_iCurrentTemplateInstance >= iMax )
{
// We won't hit this.
Assert(0);
// Hopefully there were still be instance number 0 around.
g_iCurrentTemplateInstance = 0;
}
}
//-----------------------------------------------------------------------------
// Purpose: Someone wants to spawn an instance of a template that requires
// entity IO fixup. Fill out the pMapData with a copy of the template
// with unique key/values where the template requires them.
//-----------------------------------------------------------------------------
char *Templates_GetEntityIOFixedMapData( int iIndex )
{
Assert( Templates_IndexRequiresEntityIOFixup( iIndex ) );
// First time through?
if ( !g_Templates[iIndex]->pszFixedMapData )
{
g_Templates[iIndex]->pszFixedMapData = new char[g_Templates[iIndex]->iMapDataLength];
Q_strncpy( g_Templates[iIndex]->pszFixedMapData, g_Templates[iIndex]->pszMapData, g_Templates[iIndex]->iMapDataLength );
}
int iFixupSize = strlen(ENTITYIO_FIXUP_STRING); // strlen("&0000\0") = 5!
char *sOurFixup = new char[iFixupSize+1]; // do alloc room here for the null terminator
Q_snprintf( sOurFixup, iFixupSize+1, "%c%.4d", ENTITYIO_FIXUP_STRING[0], g_iCurrentTemplateInstance );
// Now rip through the map data string and replace any instances of the fixup string with our unique identifier
char *c = g_Templates[iIndex]->pszFixedMapData;
do
{
if ( *c == ENTITYIO_FIXUP_STRING[0] )
{
// Make sure it's our fixup string
bool bValid = true;
for ( int i = 1; i < iFixupSize; i++ )
{
// Look for any number, because we've already used this string
if ( !(*(c+i) >= '0' && *(c+i) <= '9') )
{
// Some other string
bValid = false;
break;
}
}
// Stomp it with our unique string
if ( bValid )
{
memcpy( c, sOurFixup, iFixupSize );
c += iFixupSize;
}
}
c++;
} while (*c);
delete [] sOurFixup;
return g_Templates[iIndex]->pszFixedMapData;
}
//-----------------------------------------------------------------------------
// Purpose: Frees all the template data. Called on level shutdown.
//-----------------------------------------------------------------------------
void Templates_RemoveAll(void)
{
int nCount = g_Templates.Count();
for (int i = 0; i < nCount; i++)
{
TemplateEntityData_t *pTemplate = g_Templates.Element(i);
Templates_FreeTemplate( pTemplate );
}
g_Templates.RemoveAll();
}
//-----------------------------------------------------------------------------
// Purpose: Hooks in the template manager's callbacks.
//-----------------------------------------------------------------------------
class CTemplatesHook : public CAutoGameSystem
{
public:
CTemplatesHook( char const *name ) : CAutoGameSystem( name )
{
}
virtual void LevelShutdownPostEntity( void )
{
Templates_RemoveAll();
}
};
CTemplatesHook g_TemplateEntityHook( "CTemplatesHook" );
//-----------------------------------------------------------------------------
// TEMPLATE SAVE / RESTORE
//-----------------------------------------------------------------------------
static short TEMPLATE_SAVE_RESTORE_VERSION = 1;
class CTemplate_SaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler
{
public:
const char *GetBlockName()
{
return "Templates";
}
//---------------------------------
void Save( ISave *pSave )
{
pSave->WriteInt( &g_iCurrentTemplateInstance );
short nCount = g_Templates.Count();
pSave->WriteShort( &nCount );
for ( int i = 0; i < nCount; i++ )
{
TemplateEntityData_t *pTemplate = g_Templates[i];
pSave->WriteAll( pTemplate );
pSave->WriteString( pTemplate->pszName );
pSave->WriteString( pTemplate->pszMapData );
}
}
//---------------------------------
void WriteSaveHeaders( ISave *pSave )
{
pSave->WriteShort( &TEMPLATE_SAVE_RESTORE_VERSION );
}
//---------------------------------
void ReadRestoreHeaders( IRestore *pRestore )
{
// No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so.
short version;
pRestore->ReadShort( &version );
m_fDoLoad = ( version == TEMPLATE_SAVE_RESTORE_VERSION );
}
//---------------------------------
void Restore( IRestore *pRestore, bool createPlayers )
{
if ( m_fDoLoad )
{
Templates_RemoveAll();
g_Templates.Purge();
g_iCurrentTemplateInstance = pRestore->ReadInt();
int iTemplates = pRestore->ReadShort();
while ( iTemplates-- )
{
TemplateEntityData_t *pNewTemplate = (TemplateEntityData_t *)malloc(sizeof(TemplateEntityData_t));
pRestore->ReadAll( pNewTemplate );
int sizeData = 0;//pRestore->SkipHeader();
char szName[MAPKEY_MAXLENGTH];
pRestore->ReadString( szName, MAPKEY_MAXLENGTH, sizeData );
pNewTemplate->pszName = strdup( szName );
//sizeData = pRestore->SkipHeader();
pNewTemplate->pszMapData = (char *)malloc( pNewTemplate->iMapDataLength );
pRestore->ReadString( pNewTemplate->pszMapData, pNewTemplate->iMapDataLength, sizeData );
// Set this to NULL so it'll be created the first time it gets used
pNewTemplate->pszFixedMapData = NULL;
g_Templates.AddToTail( pNewTemplate );
}
}
}
private:
bool m_fDoLoad;
};
//-----------------------------------------------------------------------------
CTemplate_SaveRestoreBlockHandler g_Template_SaveRestoreBlockHandler;
//-------------------------------------
ISaveRestoreBlockHandler *GetTemplateSaveRestoreBlockHandler()
{
return &g_Template_SaveRestoreBlockHandler;
}
+39
View File
@@ -0,0 +1,39 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Template entities are used by spawners to create copies of entities
// that were configured by the level designer. This allows us to spawn
// entities with arbitrary sets of key/value data and entity I/O
// connections.
//
//=============================================================================//
#ifndef TEMPLATEENTITIES_H
#define TEMPLATEENTITIES_H
#ifdef _WIN32
#pragma once
#endif
#include "isaverestore.h"
class CBaseEntity;
class CPointTemplate;
int Templates_Add(CBaseEntity *pEntity, const char *pszMapData, int nLen, int nHammerID=-1);
string_t Templates_FindByIndex( int iIndex );
int Templates_GetStringSize( int iIndex );
string_t Templates_FindByTargetName(const char *pszName);
void Templates_ReconnectIOForGroup( CPointTemplate *pGroup );
// Some templates have Entity I/O connecting the entities within the template.
// Unique versions of these templates need to be created whenever they're instanced.
void Templates_StartUniqueInstance( void );
bool Templates_IndexRequiresEntityIOFixup( int iIndex );
char *Templates_GetEntityIOFixedMapData( int iIndex );
// Used by Foundry.
void Templates_RemoveByHammerID( int nHammerID );
// Save / Restore
ISaveRestoreBlockHandler *GetTemplateSaveRestoreBlockHandler( void );
#endif // TEMPLATEENTITIES_H
+118
View File
@@ -0,0 +1,118 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Shadow control entity.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//------------------------------------------------------------------------------
// FIXME: This really should inherit from something more lightweight
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Purpose : Water LOD control entity
//------------------------------------------------------------------------------
class CWaterLODControl : public CBaseEntity
{
public:
DECLARE_CLASS( CWaterLODControl, CBaseEntity );
CWaterLODControl();
void Spawn( void );
bool KeyValue( const char *szKeyName, const char *szValue );
int UpdateTransmitState();
void SetCheapWaterStartDistance( inputdata_t &inputdata );
void SetCheapWaterEndDistance( inputdata_t &inputdata );
virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
private:
CNetworkVar( float, m_flCheapWaterStartDistance );
CNetworkVar( float, m_flCheapWaterEndDistance );
};
LINK_ENTITY_TO_CLASS(water_lod_control, CWaterLODControl);
BEGIN_DATADESC( CWaterLODControl )
DEFINE_KEYFIELD( m_flCheapWaterStartDistance, FIELD_FLOAT, "cheapwaterstartdistance" ),
DEFINE_KEYFIELD( m_flCheapWaterEndDistance, FIELD_FLOAT, "cheapwaterenddistance" ),
// Inputs
DEFINE_INPUT( m_flCheapWaterStartDistance, FIELD_FLOAT, "SetCheapWaterStartDistance" ),
DEFINE_INPUT( m_flCheapWaterEndDistance, FIELD_FLOAT, "SetCheapWaterEndDistance" ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST_NOBASE(CWaterLODControl, DT_WaterLODControl)
SendPropFloat(SENDINFO(m_flCheapWaterStartDistance), 0, SPROP_NOSCALE ),
SendPropFloat(SENDINFO(m_flCheapWaterEndDistance), 0, SPROP_NOSCALE ),
END_SEND_TABLE()
CWaterLODControl::CWaterLODControl()
{
m_flCheapWaterStartDistance = 1000.0f;
m_flCheapWaterEndDistance = 2000.0f;
}
//------------------------------------------------------------------------------
// Purpose : Send even though we don't have a model
//------------------------------------------------------------------------------
int CWaterLODControl::UpdateTransmitState()
{
// ALWAYS transmit to all clients.
return SetTransmitState( FL_EDICT_ALWAYS );
}
bool CWaterLODControl::KeyValue( const char *szKeyName, const char *szValue )
{
if ( FStrEq( szKeyName, "cheapwaterstartdistance" ) )
{
m_flCheapWaterStartDistance = atof( szValue );
return true;
}
if ( FStrEq( szKeyName, "cheapwaterenddistance" ) )
{
m_flCheapWaterEndDistance = atof( szValue );
return true;
}
return BaseClass::KeyValue( szKeyName, szValue );
}
//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CWaterLODControl::Spawn( void )
{
Precache();
SetSolid( SOLID_NONE );
}
//------------------------------------------------------------------------------
// Input values
//------------------------------------------------------------------------------
void CWaterLODControl::SetCheapWaterStartDistance( inputdata_t &inputdata )
{
m_flCheapWaterStartDistance = atof( inputdata.value.String() );
}
void CWaterLODControl::SetCheapWaterEndDistance( inputdata_t &inputdata )
{
m_flCheapWaterEndDistance = atof( inputdata.value.String() );
}
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "actanimating.h"
#include "animation.h"
#include "activitylist.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
BEGIN_DATADESC( CActAnimating )
DEFINE_CUSTOM_FIELD( m_Activity, ActivityDataOps() ),
END_DATADESC()
void CActAnimating::SetActivity( Activity act )
{
int sequence = SelectWeightedSequence( act );
if ( sequence != ACTIVITY_NOT_AVAILABLE )
{
ResetSequence( sequence );
m_Activity = act;
SetCycle( 0 );
}
}
+35
View File
@@ -0,0 +1,35 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef ACTANIMATING_H
#define ACTANIMATING_H
#ifdef _WIN32
#pragma once
#endif
#include "baseanimating.h"
class CActAnimating : public CBaseAnimating
{
public:
DECLARE_CLASS( CActAnimating, CBaseAnimating );
void SetActivity( Activity act );
inline Activity GetActivity( void ) { return m_Activity; }
virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
DECLARE_DATADESC();
private:
Activity m_Activity;
};
#endif // ACTANIMATING_H
File diff suppressed because it is too large Load Diff
+580
View File
@@ -0,0 +1,580 @@
// ai_addon.cpp
#include "cbase.h"
#include "ai_addon.h"
#include "ai_basenpc.h"
#include "ai_behavior_addonhost.h"
#include "strtools.h"
//---------------------------------------------------------
// Answers YES if the attachment is on the model and not
// currently plugged by another add-on.
//
// Answers NO if the host's model lacks the attachment or
// the add-on is currently plugged by another add-on.
//
// This is terribly inefficient right now, but it works
// and lets us move on.
//---------------------------------------------------------
bool IsAddOnAttachmentAvailable( CAI_BaseNPC *pHost, char *pszAttachmentName )
{
char szOrigialAttachmentName[ 256 ];
V_strcpy_safe( szOrigialAttachmentName, pszAttachmentName );
int iCount = 0;
// If this loops 20 times, the translate function probably isn't returning the end of list value "" -Jeep
while ( iCount < 20 )
{
// Try another
Q_strcpy( pszAttachmentName, szOrigialAttachmentName );
pHost->TranslateAddOnAttachment( pszAttachmentName, iCount );
if ( pszAttachmentName[ 0 ] == '\0' )
{
return false;
}
if ( pHost->LookupAttachment(pszAttachmentName) == 0 )
{
// Translated to an attachment that doesn't exist
Msg("***AddOn Error! Host NPC %s does not have attachment %s\n", pHost->GetDebugName(), pszAttachmentName );
return false;
}
int iWishedAttachmentID = pHost->LookupAttachment( pszAttachmentName );
CAI_BehaviorBase **ppBehaviors = pHost->AccessBehaviors();
int nBehaviors = pHost->NumBehaviors();
bool bAttachmentFilled = false;
for ( int i = 0; i < nBehaviors && !bAttachmentFilled; i++ )
{
CAI_AddOnBehaviorBase *pAddOnBehavior = dynamic_cast<CAI_AddOnBehaviorBase *>(ppBehaviors[i]);
if ( pAddOnBehavior )
{
CAI_AddOn **ppAddOns = pAddOnBehavior->GetAddOnsBase();
int nAddOns = pAddOnBehavior->NumAddOns();
for ( int j = 0; j < nAddOns && !bAttachmentFilled; j++ )
{
bAttachmentFilled = ( ppAddOns[j]->GetAttachmentID() == iWishedAttachmentID );
}
}
}
if ( !bAttachmentFilled )
{
return true;
}
++iCount;
}
// We should never get here
DevWarning( "Translating the attachment was tried more than 50 times!\n" );
return false;
}
//---------------------------------------------------------
//---------------------------------------------------------
int CountAddOns( CAI_BaseNPC *pHost )
{
int nAddOns = 0;
CAI_BehaviorBase **ppBehaviors = pHost->AccessBehaviors();
int nBehaviors = pHost->NumBehaviors();
for ( int i = 0; i < nBehaviors; i++ )
{
CAI_AddOnBehaviorBase *pAddOnBehavior = dynamic_cast<CAI_AddOnBehaviorBase *>(ppBehaviors[i]);
if ( pAddOnBehavior )
{
nAddOns += pAddOnBehavior->NumAddOns();
}
}
return nAddOns;
}
//---------------------------------------------------------
//---------------------------------------------------------
BEGIN_DATADESC( CAI_AddOn )
DEFINE_FIELD( m_hNPCHost, FIELD_EHANDLE ),
DEFINE_THINKFUNC( DispatchAddOnThink ),
DEFINE_FIELD( m_hPhysReplacement, FIELD_EHANDLE ),
DEFINE_FIELD( m_iPhysReplacementSolidFlags, FIELD_INTEGER ),
DEFINE_FIELD( m_iPhysReplacementMoveType, FIELD_INTEGER ),
DEFINE_FIELD( m_angPhysReplacementLocalOrientation, FIELD_VECTOR ),
DEFINE_FIELD( m_vecPhysReplacementDetatchForce, FIELD_VECTOR ),
DEFINE_FIELD( m_bWasAttached, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flWaitFinished, FIELD_TIME ),
DEFINE_FIELD( m_flNextAttachTime, FIELD_FLOAT ),
DEFINE_INPUTFUNC( FIELD_STRING, "Install", InputInstall ),
DEFINE_INPUTFUNC( FIELD_VOID, "Remove", InputRemove ),
END_DATADESC()
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::Precache()
{
BaseClass::Precache();
PrecacheModel( GetAddOnModelName() );
PrecacheScriptSound( "AddOn.Install" );
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::Spawn()
{
BaseClass::Spawn();
Precache();
CBaseEntity *pPhysReplacement = m_hPhysReplacement.Get();
if ( pPhysReplacement )
{
// Use the same model as the replacement
SetModel( pPhysReplacement->GetModelName().ToCStr() );
}
else
{
SetModel( GetAddOnModelName() );
}
SetCollisionGroup( COLLISION_GROUP_WEAPON );
VPhysicsInitNormal( SOLID_VPHYSICS, GetSolidFlags() | FSOLID_TRIGGER, false );
SetMoveType( MOVETYPE_VPHYSICS );
SetThink( &CAI_AddOn::DispatchAddOnThink );
SetNextThink( gpGlobals->curtime + 0.1f );
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::UpdateOnRemove()
{
Remove();
BaseClass::UpdateOnRemove();
}
int CAI_AddOn::DrawDebugTextOverlays( void )
{
int text_offset = BaseClass::DrawDebugTextOverlays();
// Draw debug text for agent
m_AgentDebugOverlays = m_debugOverlays;
Vector vecLocalCenter;
ICollideable *pCollidable = GetCollideable();
if ( pCollidable )
{
VectorAdd( pCollidable->OBBMins(), pCollidable->OBBMaxs(), vecLocalCenter );
vecLocalCenter *= 0.5f;
if ( ( pCollidable->GetCollisionAngles() == vec3_angle ) || ( vecLocalCenter == vec3_origin ) )
{
VectorAdd( vecLocalCenter, pCollidable->GetCollisionOrigin(), m_vecAgentDebugOverlaysPos );
}
else
{
VectorTransform( vecLocalCenter, pCollidable->CollisionToWorldTransform(), m_vecAgentDebugOverlaysPos );
}
}
else
{
m_vecAgentDebugOverlaysPos = GetAbsOrigin();
}
text_offset = static_cast<CAI_Agent*>( this )->DrawDebugTextOverlays( text_offset );
return text_offset;
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::GatherConditions()
{
CAI_Agent::GatherConditions();
ClearCondition( COND_ADDON_LOST_HOST );
if( GetNPCHost() )
{
m_bWasAttached = true;
}
else
{
if( m_bWasAttached == true )
{
SetCondition( COND_ADDON_LOST_HOST );
if ( m_flNextAttachTime != 0.0f && m_flNextAttachTime < gpGlobals->curtime )
{
m_flNextAttachTime = 0.0f;
m_bWasAttached = false;
}
}
}
}
//---------------------------------------------------------
//---------------------------------------------------------
int CAI_AddOn::SelectSchedule( void )
{
return SCHED_ADDON_NO_OWNER;
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::StartTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_ADDON_WAIT:
m_flWaitFinished = gpGlobals->curtime + pTask->flTaskData;
break;
case TASK_ADDON_WAIT_RANDOM:
m_flWaitFinished = gpGlobals->curtime + random->RandomFloat( 0.1f, pTask->flTaskData );
break;
default:
CAI_Agent::StartTask( pTask );
break;
}
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::RunTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_ADDON_WAIT:
case TASK_ADDON_WAIT_RANDOM:
if( gpGlobals->curtime > m_flWaitFinished )
TaskComplete();
break;
default:
CAI_Agent::RunTask( pTask );
break;
}
}
void CAI_AddOn::SetPhysReplacement( CBaseEntity *pEntity )
{
m_hPhysReplacement = pEntity;
}
//---------------------------------------------------------
//---------------------------------------------------------
bool CAI_AddOn::Attach( CAI_BaseNPC *pHost )
{
// Make sure we're not already attached to someone else!
Assert( GetAttachmentID() == INVALID_ADDON_ATTACHMENT_ID );
char szAttachmentName[ 256 ];
szAttachmentName[ 0 ] = '\0';
PickAttachment( pHost, szAttachmentName );
if ( szAttachmentName[ 0 ] == '\0' )
{
return false;
}
int iAttachmentIndex = pHost->LookupAttachment( szAttachmentName );
if ( !iAttachmentIndex )
{
return false;
}
Vector vecOrigin;
Vector vecForward, vecRight, vecUp;
QAngle angles;
pHost->GetAttachment( iAttachmentIndex, vecOrigin, angles );
AngleVectors( angles, &vecForward, &vecRight, &vecUp );
SetAbsOrigin( vecOrigin + GetAttachOffset( angles ) );
SetAbsAngles( GetAttachOrientation( angles ) );
m_iAttachmentID = iAttachmentIndex;
SetParent( pHost, iAttachmentIndex );
QAngle angLocalAngles = GetLocalOrientation();
SetLocalAngles( angLocalAngles );
// Stop acting physical
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject )
{
pPhysicsObject->EnableMotion( false );
pPhysicsObject->EnableCollisions( false );
}
SetMoveType( MOVETYPE_NONE );
// Handle the phys replacement
CBaseEntity *pPhysReplacement = m_hPhysReplacement.Get();
if ( pPhysReplacement )
{
CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
pPlayer->ForceDropOfCarriedPhysObjects( pPhysReplacement );
pPhysReplacement->AddEffects( EF_NODRAW );
m_iPhysReplacementSolidFlags = pPhysReplacement->GetSolidFlags();
pPhysReplacement->SetSolidFlags( FSOLID_NOT_SOLID );
m_iPhysReplacementMoveType = pPhysReplacement->GetMoveType();
pPhysReplacement->SetMoveType( MOVETYPE_NONE );
pPhysReplacement->SetAbsOrigin( vecOrigin + GetAttachOffset( angles ) );
pPhysReplacement->SetAbsAngles( GetAttachOrientation( angles ) );
pPhysReplacement->SetParent( pHost, iAttachmentIndex );
pPhysReplacement->SetOwnerEntity( pHost );
m_angPhysReplacementLocalOrientation = pPhysReplacement->GetLocalAngles();
pPhysReplacement->SetLocalAngles( angLocalAngles );
IPhysicsObject *pReplacementPhysObject = pPhysReplacement->VPhysicsGetObject();
if ( pReplacementPhysObject )
{
pReplacementPhysObject->EnableMotion( false );
pReplacementPhysObject->EnableCollisions( false );
SetMoveType( MOVETYPE_NONE );
}
}
return true;
}
void CAI_AddOn::Dettach( void )
{
if ( !m_bWasAttached )
return;
m_flNextAttachTime = gpGlobals->curtime + 2.0f;
m_hNPCHost.Set( NULL );
SetParent( NULL );
IPhysicsObject *pPhysObject = NULL;
CBaseEntity *pPhysReplacement = m_hPhysReplacement.Get();
if ( pPhysReplacement )
{
// Make the replacement visible
pPhysReplacement->RemoveEffects( EF_NODRAW );
pPhysReplacement->SetSolidFlags( m_iPhysReplacementSolidFlags );
pPhysReplacement->SetMoveType( MoveType_t( m_iPhysReplacementMoveType ) );
pPhysObject = pPhysReplacement->VPhysicsGetObject();
if ( pPhysObject )
{
pPhysReplacement->SetMoveType( MOVETYPE_VPHYSICS );
}
pPhysReplacement->SetParent( NULL );
pPhysReplacement->SetOwnerEntity( NULL );
pPhysReplacement->SetLocalAngles( m_angPhysReplacementLocalOrientation );
// Kill ourselves off because we're being replaced
UTIL_Remove( this );
}
else
{
pPhysObject = VPhysicsGetObject();
if ( pPhysObject )
{
SetMoveType( MOVETYPE_VPHYSICS );
}
}
if ( pPhysObject )
{
// Start acting physical
pPhysObject->EnableCollisions( true );
pPhysObject->EnableMotion( true );
pPhysObject->EnableGravity( true );
pPhysObject->SetPosition( GetAbsOrigin(), GetAbsAngles(), true );
pPhysObject->Wake();
pPhysObject->AddVelocity( &m_vecPhysReplacementDetatchForce, NULL );
}
}
//---------------------------------------------------------
// Return true if I successfully attach to the NPC host.
//
// Return false if I am already attached to an NPC, or
// could not be attached to this host.
//---------------------------------------------------------
bool CAI_AddOn::Install( CAI_BaseNPC *pHost, bool bRemoveOnFail )
{
if( m_bWasAttached )
return false;
// Associate the addon with this host
Assert( m_hNPCHost == NULL ); // For now, prevent slamming from one host to the next.
m_hNPCHost.Set( pHost );
// Parent and
if ( Attach( pHost ) )
{
Bind();
return true;
}
// Failed to attach
m_hNPCHost = NULL;
if ( bRemoveOnFail || m_hPhysReplacement.Get() )
{
UTIL_Remove( this );
}
return false;
}
//---------------------------------------------------------
//---------------------------------------------------------
CAI_BaseNPC *CAI_AddOn::GetNPCHost()
{
return m_hNPCHost.Get();
}
//---------------------------------------------------------
//---------------------------------------------------------
CBaseEntity *CAI_AddOn::GetHostEnemy()
{
if( !GetNPCHost() )
return NULL;
return GetNPCHost()->GetEnemy();
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::DispatchAddOnThink()
{
if( GetNPCHost() != NULL && !GetNPCHost()->IsAlive() )
{
EjectFromHost();
return;
}
CAI_Agent::Think(); SetNextThink( gpGlobals->curtime + GetThinkInterval() );
}
QAngle CAI_AddOn::GetLocalOrientation( void )
{
CBaseEntity *pPhysReplacement = m_hPhysReplacement.Get();
if ( pPhysReplacement )
{
CBaseAnimating *pBaseAnimatingReplacement = dynamic_cast<CBaseAnimating *>( pPhysReplacement );
if ( pBaseAnimatingReplacement )
{
int iMuzzle = pBaseAnimatingReplacement->LookupAttachment( "muzzle" );
if ( iMuzzle )
{
// Use the muzzle angles!
Vector vecMuzzleOrigin;
QAngle angMuzzleAngles;
pBaseAnimatingReplacement->GetAttachmentLocal( iMuzzle, vecMuzzleOrigin, angMuzzleAngles );
return angMuzzleAngles;
}
}
// Use the local angles
return pPhysReplacement->GetLocalAngles();
}
// No special angles to use
return QAngle( 0.0f, 0.0f, 0.0f );
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::EjectFromHost()
{
Unbind();
m_hNPCHost.Set( NULL );
SetThink( NULL );
SetParent( NULL );
SetSize( Vector( 0,0,0), Vector(0,0,0) );
SetMoveType( MOVETYPE_FLYGRAVITY );
SetMoveCollide( MOVECOLLIDE_FLY_BOUNCE );
SetSolid( SOLID_BBOX );
Vector vecDir;
GetVectors( NULL, NULL, &vecDir );
SetAbsVelocity( GetAbsVelocity() + vecDir * RandomFloat(50, 200) );
QAngle avelocity( RandomFloat( 10, 60), RandomFloat( 10, 60), 0 );
SetLocalAngularVelocity( avelocity );
SetThink( &CBaseEntity::SUB_FadeOut );
SetNextThink( gpGlobals->curtime + 1.0f );
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::InputInstall( inputdata_t &data )
{
CAI_BaseNPC *pHost = dynamic_cast<CAI_BaseNPC *>( gEntList.FindEntityByName( NULL, data.value.String() ) );
if( !pHost )
{
DevMsg(" AddOn: %s couldn't find Host %s\n", GetDebugName(), data.value.String() );
}
else
{
Install( pHost );
}
}
//---------------------------------------------------------
//---------------------------------------------------------
void CAI_AddOn::InputRemove( inputdata_t &data )
{
Remove();
m_hNPCHost.Set( NULL );
SetThink( NULL );
SetParent( NULL );
UTIL_Remove( this );
}
AI_BEGIN_AGENT_(CAI_AddOn,CAI_Agent)
DECLARE_TASK( TASK_ADDON_WAIT )
DECLARE_TASK( TASK_ADDON_WAIT_RANDOM )
DECLARE_CONDITION( COND_ADDON_LOST_HOST )
DEFINE_SCHEDULE
(
SCHED_ADDON_NO_OWNER,
" Tasks"
" TASK_ADDON_WAIT 1"
" "
" Interrupts"
" "
)
AI_END_AGENT()
+344
View File
@@ -0,0 +1,344 @@
// ai_addon.h
#ifndef AI_ADDON_H
#define AI_ADDON_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/utlvector.h"
#include "baseanimating.h"
#include "ai_agent.h"
#include "ai_behavior.h"
#include "player_pickup.h"
#include "IEffects.h"
class CAI_BaseNPC;
#define INVALID_ADDON_ATTACHMENT_ID -1
int GetIDForAttachmentName( char *pszAttachmentName );
int GetNumAddOnAttachmentPoints();
bool IsAddOnAttachmentAvailable( CAI_BaseNPC *pHost, char *pszAttachmentName );
int CountAddOns( CAI_BaseNPC *pHost );
//=========================================================
//=========================================================
class CAI_AddOn : public CBaseAnimating, public CAI_Agent, public CDefaultPlayerPickupVPhysics
{
public:
DECLARE_CLASS( CAI_AddOn, CBaseAnimating );
//---------------------------------
// CTor/DTor
//---------------------------------
CAI_AddOn() { m_iAttachmentID = INVALID_ADDON_ATTACHMENT_ID; }
//---------------------------------
// Precache/Spawn/etc
//---------------------------------
virtual void Precache();
virtual void Spawn();
virtual void UpdateOnRemove();
const char *GetDebugName() { return CBaseEntity::GetDebugName(); }
int entindex() { return CBaseEntity::entindex(); }
virtual int DrawDebugTextOverlays(void);
//---------------------------------
//---------------------------------
bool SUB_AllowedToFade( void )
{
return true;
}
int Save( ISave &save )
{
int result = CBaseAnimating::Save( save );
if ( result )
{
result = CAI_Agent::Save( save );
}
return result;
}
//-------------------------------------
int Restore( IRestore &restore )
{
int result = CBaseAnimating::Restore( restore );
if ( result )
{
result = CAI_Agent::Restore( restore );
}
return result;
}
//---------------------------------
// Agent stuff
//---------------------------------
virtual void GatherConditions();
virtual int SelectSchedule();
virtual void StartTask( const Task_t *pTask );
virtual void RunTask( const Task_t *pTask );
//---------------------------------
// Install/Remove AddOns
//---------------------------------
virtual bool Install( CAI_BaseNPC *pHost, bool bRemoveOnFail = true );
void SetPhysReplacement( CBaseEntity *pEntity );
bool Attach( CAI_BaseNPC *pHost );
void Dettach( void );
virtual void Remove() { Unbind(); Dettach(); }
virtual void Bind() {}
virtual void Unbind() {}
//---------------------------------
// Hosts/Outer accessors
//---------------------------------
CAI_BaseNPC *GetNPCHost();
CBaseEntity *GetHostEnemy();
//---------------------------------
// Thinking
//---------------------------------
void DispatchAddOnThink();
virtual float GetThinkInterval() { return 0.1f; }
//---------------------------------
// Appearance & Position
//---------------------------------
virtual void PickAttachment( CAI_BaseNPC *pHost, char *pchAttachment ) = 0;
virtual char *GetAddOnModelName() = 0;
virtual Vector GetAttachOffset( QAngle &attachmentAngles ) { return vec3_origin; }
virtual QAngle GetAttachOrientation( QAngle &attachmentAngles ) { return attachmentAngles; }
virtual QAngle GetLocalOrientation( void );
int GetAttachmentID() { return m_iAttachmentID; }
virtual void EjectFromHost();
//---------------------------------
// Entity I/O
//---------------------------------
void InputInstall( inputdata_t &data );
void InputRemove( inputdata_t &data );
//---------------------------------
// Schedule/Task/Conditions
//---------------------------------
enum
{
TASK_ADDON_WAIT = NEXT_TASK,
TASK_ADDON_WAIT_RANDOM,
NEXT_TASK,
};
enum
{
COND_ADDON_LOST_HOST = NEXT_CONDITION,
NEXT_CONDITION,
};
enum
{
SCHED_ADDON_NO_OWNER = NEXT_SCHEDULE,
NEXT_SCHEDULE,
};
DECLARE_DATADESC();
protected:
CHandle<CAI_BaseNPC> m_hNPCHost;
CHandle<CBaseEntity> m_hPhysReplacement;
int m_iPhysReplacementSolidFlags;
int m_iPhysReplacementMoveType;
QAngle m_angPhysReplacementLocalOrientation;
Vector m_vecPhysReplacementDetatchForce;
bool m_bWasAttached;
private:
//---------------------------------
// Fields used by AI
//---------------------------------
float m_flWaitFinished; // Same as for AI_BaseNPC
int m_iAttachmentID; // Which attachment point am I connected to on my host's model?
float m_flNextAttachTime;
public:
//---------------------------------
// Hammer keyfields
//---------------------------------
DEFINE_AGENT();
};
//=========================================================
//=========================================================
class CAI_AddOnBehaviorBase : public CAI_SimpleBehavior
{
public:
virtual bool ShouldNPCSave()
{
return false;
}
virtual CAI_AddOn **GetAddOnsBase() { return NULL; }
const CAI_AddOn **GetAddOnsBase() const { return (const CAI_AddOn **)const_cast<CAI_AddOnBehaviorBase *>(this)->GetAddOnsBase(); }
virtual int NumAddOns() const { return 0; }
};
template <class ADDON>
class CAI_AddOnBehavior : public CAI_AddOnBehaviorBase
{
public:
virtual int BindAddOn( ADDON *pAddOn )
{
Assert( m_AddOns.Find( pAddOn ) == m_AddOns.InvalidIndex() );
Assert( static_cast<CAI_AddOn *>(pAddOn) ); // Satic cast is used to trap improper inheritance
m_AddOns.AddToTail( pAddOn );
return m_AddOns.Count();
}
virtual int UnbindAddOn( ADDON *pAddOn )
{
Assert( m_AddOns.Find( pAddOn ) != m_AddOns.InvalidIndex() );
m_AddOns.FindAndRemove( pAddOn );
return m_AddOns.Count();
}
virtual CAI_AddOn **GetAddOnsBase()
{
return (CAI_AddOn **)m_AddOns.Base();
}
virtual ADDON **GetAddOns()
{
return m_AddOns.Base();
}
virtual int NumAddOns() const
{
return m_AddOns.Count();
}
protected:
CUtlVector<ADDON *> m_AddOns;
};
//=========================================================
//=========================================================
template <class ADDON, class BEHAVIOR>
class CAI_AddOnBehaviorConnector : public ADDON
{
DECLARE_CLASS( CAI_AddOnBehaviorConnector, ADDON );
virtual int ObjectCaps( void )
{
return BaseClass::ObjectCaps() | FCAP_IMPULSE_USE;
}
virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
CBasePlayer *pPlayer = ToBasePlayer( pActivator );
if ( pPlayer == NULL )
return;
if ( this->m_bWasAttached )
{
this->Remove();
g_pEffects->Sparks( this->GetAbsOrigin(), 2, 2 );
}
pPlayer->PickupObject( this );
}
virtual void Bind()
{
if ( this->m_hNPCHost )
{
BEHAVIOR *pBehavior;
if ( !this->m_hNPCHost->GetBehavior( &pBehavior ) )
{
pBehavior = new BEHAVIOR;
this->m_hNPCHost->AddBehavior( pBehavior );
}
pBehavior->BindAddOn( this );
}
}
virtual void Unbind()
{
if ( this->m_hNPCHost )
{
BEHAVIOR *pBehavior;
if ( this->m_hNPCHost->GetBehavior( &pBehavior ) )
{
if ( pBehavior->UnbindAddOn( this ) == 0 )
{
this->m_hNPCHost->RemoveAndDestroyBehavior( pBehavior );
}
}
}
}
//-------------------------------------
int Save( ISave &save )
{
int result = BaseClass::Save( save );
if ( result )
{
bool bSaved = false;
BEHAVIOR *pBehavior;
if ( this->m_hNPCHost && this->m_hNPCHost->GetBehavior( &pBehavior ) )
{
bSaved = true;
CAI_BehaviorBase::SaveBehaviors( save, pBehavior, (CAI_BehaviorBase **)&pBehavior, 1, false );
}
if ( !bSaved )
{
CAI_BehaviorBase::SaveBehaviors( save, NULL, NULL, 0 );
}
}
return result;
}
//-------------------------------------
int Restore( IRestore &restore )
{
int result = BaseClass::Restore( restore );
if ( result )
{
Bind();
BEHAVIOR *pBehavior;
if ( this->m_hNPCHost && this->m_hNPCHost->GetBehavior( &pBehavior ) )
{
CAI_BehaviorBase::RestoreBehaviors( restore, (CAI_BehaviorBase **)&pBehavior, 1, false );
}
else
{
CAI_BehaviorBase::RestoreBehaviors( restore, NULL, 0,false );
}
}
return result;
}
};
#define LINK_ENTITY_TO_ADDON_AND_BEHAVIOR( mapClassName, DLLClassName, BehaviorName ) \
typedef CAI_AddOnBehaviorConnector<DLLClassName, BehaviorName> DLLClassName##_##BehaviorName##_Binder; \
LINK_ENTITY_TO_CLASS( mapClassName, DLLClassName##_##BehaviorName##_Binder )
#endif // AI_ADDON_H
File diff suppressed because it is too large Load Diff
+641
View File
@@ -0,0 +1,641 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Base NPC character with AI
//
//=============================================================================//
#ifndef AI_AGENT_H
#define AI_AGENT_H
#ifdef _WIN32
#pragma once
#endif
#include "ai_debug.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_condition.h"
#include "ai_task.h"
#include "ai_namespaces.h"
#include "bitstring.h"
class CAI_Agent;
#ifndef DBGFLAG_STRINGS_STRIP
void DevMsg( CAI_Agent *pAI, unsigned flags, PRINTF_FORMAT_STRING const char *pszFormat, ... ) FMTFUNCTION( 3, 4 );
void DevMsg( CAI_Agent *pAI, PRINTF_FORMAT_STRING const char *pszFormat, ... ) FMTFUNCTION( 2, 3 );
#endif
typedef CBitVec<MAX_CONDITIONS> CAI_ScheduleBits;
//=============================================================================
//
// Constants & enumerations
//
//=============================================================================
//
// Debug bits
//
//-------------------------------------
#ifdef AI_MONITOR_FOR_OSCILLATION
struct AIScheduleChoice_t
{
float m_flTimeSelected;
CAI_Schedule *m_pScheduleSelected;
};
#endif//AI_MONITOR_FOR_OSCILLATION
//=============================================================================
//
// Types used by CAI_Agent
//
//=============================================================================
struct AIAgentScheduleState_t
{
int iCurTask;
TaskStatus_e fTaskStatus;
float timeStarted;
float timeCurTaskStarted;
AI_TaskFailureCode_t taskFailureCode;
int iTaskInterrupt;
bool bScheduleWasInterrupted;
DECLARE_SIMPLE_DATADESC();
};
//=============================================================================
//
// class CAI_Agent
//
//=============================================================================
class CAI_Agent
{
public:
//-----------------------------------------------------
//
// Initialization, cleanup, serialization, identity
//
CAI_Agent();
~CAI_Agent();
//---------------------------------
DECLARE_SIMPLE_DATADESC();
virtual int Save( ISave &save );
virtual int Restore( IRestore &restore );
void SaveConditions( ISave &save, const CAI_ScheduleBits &conditions );
void RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions );
//---------------------------------
virtual void Init( void ); // derived calls after Spawn()
// Flaccid implementations to satisfy boilerplate debug code
virtual const char *GetDebugName() { return "CAI_Agent"; }
virtual int entindex() { return -1; }
public:
//-----------------------------------------------------
//
// AI processing - thinking, schedule selection and task running
//
//-----------------------------------------------------
// Thinking, including core thinking, movement, animation
virtual void Think( void );
// Core thinking (schedules & tasks)
virtual void RunAI( void );// core ai function!
// Called to gather up all relevant conditons
virtual void GatherConditions( void );
// Called immediately prior to schedule processing
virtual void PrescheduleThink( void );
// Called immediately after schedule processing
virtual void PostscheduleThink( void ) { return; };
// Notification that the current schedule, if any, is ending and a new one is being selected
virtual void OnScheduleChange( void );
// Notification that a new schedule is about to run its first task
virtual void OnStartSchedule( int scheduleType ) {};
// This function implements a decision tree for the NPC. It is responsible for choosing the next behavior (schedule)
// based on the current conditions and state.
virtual int SelectSchedule( void );
virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode );
// After the schedule has been selected, it will be processed by this function so child NPC classes can
// remap base schedules into child-specific behaviors
virtual int TranslateSchedule( int scheduleType ) { return scheduleType; }
virtual void StartTask( const Task_t *pTask );
virtual void RunTask( const Task_t *pTask );
virtual void ClearTransientConditions();
void ForceGatherConditions() { m_bForceConditionsGather = true; } // Force an NPC out of PVS to call GatherConditions on next think
enum
{
SCHED_NONE = 0,
SCHED_FAIL,
NEXT_SCHEDULE,
TASK_INVALID = 0,
TASK_SET_SCHEDULE,
NEXT_TASK,
COND_NONE = 0, // A way for a function to return no condition to get
COND_TASK_FAILED,
COND_SCHEDULE_DONE,
COND_NO_CUSTOM_INTERRUPTS, // Don't call BuildScheduleTestBits for this schedule. Used for schedules that must strictly control their interruptibility.
NEXT_CONDITION,
};
protected:
// Used by derived classes to chain a task to a task that might not be the
// one they are currently handling:
void ChainStartTask( int task, float taskData = 0 ) { Task_t tempTask = { task, taskData }; StartTask( (const Task_t *)&tempTask ); }
void ChainRunTask( int task, float taskData = 0 ) { Task_t tempTask = { task, taskData }; RunTask( (const Task_t *) &tempTask ); }
private:
bool PreThink( void );
void MaintainSchedule( void );
virtual int StartTask ( Task_t *pTask ) { DevMsg( "Called wrong StartTask()\n" ); StartTask( (const Task_t *)pTask ); return 0; } // to ensure correct signature in derived classes
virtual int RunTask ( Task_t *pTask ) { DevMsg( "Called wrong RunTask()\n" ); RunTask( (const Task_t *)pTask ); return 0; } // to ensure correct signature in derived classes
public:
//-----------------------------------------------------
//
// Schedules & tasks
//
//-----------------------------------------------------
void SetSchedule( CAI_Schedule *pNewSchedule );
bool SetSchedule( int localScheduleID );
void SetDefaultFailSchedule( int failSchedule ) { m_failSchedule = failSchedule; }
void ClearSchedule( const char *szReason );
CAI_Schedule * GetCurSchedule() { return m_pSchedule; }
bool IsCurSchedule( int schedId, bool fIdeal = true );
virtual CAI_Schedule *GetSchedule(int localScheduleID);
virtual int GetLocalScheduleId( int globalScheduleID ) { return AI_IdIsLocal( globalScheduleID ) ? globalScheduleID : GetClassScheduleIdSpace()->ScheduleGlobalToLocal( globalScheduleID ); }
virtual int GetGlobalScheduleId( int localScheduleID ) { return AI_IdIsGlobal( localScheduleID ) ? localScheduleID : GetClassScheduleIdSpace()->ScheduleLocalToGlobal( localScheduleID ); }
float GetTimeScheduleStarted() const { return m_ScheduleState.timeStarted; }
//---------------------------------
const Task_t* GetTask( void );
int TaskIsRunning( void );
virtual void TaskFail( AI_TaskFailureCode_t );
void TaskFail( const char *pszGeneralFailText ) { TaskFail( MakeFailCode( pszGeneralFailText ) ); }
void TaskComplete( bool fIgnoreSetFailedCondition = false );
void TaskInterrupt() { m_ScheduleState.iTaskInterrupt++; }
void ClearTaskInterrupt() { m_ScheduleState.iTaskInterrupt = 0; }
int GetTaskInterrupt() const { return m_ScheduleState.iTaskInterrupt; }
void TaskMovementComplete( void );
inline int TaskIsComplete( void ) { return (GetTaskStatus() == TASKSTATUS_COMPLETE); }
virtual const char *TaskName(int taskID);
float GetTimeTaskStarted() const { return m_ScheduleState.timeCurTaskStarted; }
virtual int GetLocalTaskId( int globalTaskId) { return GetClassScheduleIdSpace()->TaskGlobalToLocal( globalTaskId ); }
virtual const char *GetSchedulingErrorName() { return "CAI_Agent"; }
protected:
static bool LoadSchedules(void);
virtual bool LoadedSchedules(void);
virtual void BuildScheduleTestBits( void );
//---------------------------------
// This is the main call to select/translate a schedule
virtual CAI_Schedule *GetNewSchedule( void );
virtual CAI_Schedule *GetFailSchedule( void );
private:
// This function maps the type through TranslateSchedule() and then retrieves the pointer
// to the actual CAI_Schedule from the database of schedules available to this class.
CAI_Schedule * GetScheduleOfType( int scheduleType );
bool FHaveSchedule( void );
bool FScheduleDone ( void );
CAI_Schedule * ScheduleInList( const char *pName, CAI_Schedule **pList, int listCount );
int GetScheduleCurTaskIndex() const { return m_ScheduleState.iCurTask; }
inline int IncScheduleCurTaskIndex();
inline void ResetScheduleCurTaskIndex();
void NextScheduledTask ( void );
bool IsScheduleValid ( void );
// Selecting the ideal state
// Various schedule selections based on NPC_STATE
void OnStartTask( void ) { SetTaskStatus( TASKSTATUS_RUN_MOVE_AND_TASK ); }
void SetTaskStatus( TaskStatus_e status ) { m_ScheduleState.fTaskStatus = status; }
TaskStatus_e GetTaskStatus() const { return m_ScheduleState.fTaskStatus; }
void DiscardScheduleState();
//---------------------------------
CAI_Schedule * m_pSchedule;
int m_IdealSchedule;
AIAgentScheduleState_t m_ScheduleState;
int m_failSchedule; // Schedule type to choose if current schedule fails
public:
//-----------------------------------------------------
//
// Conditions
//
//-----------------------------------------------------
virtual const char* ConditionName(int conditionID);
virtual void RemoveIgnoredConditions ( void );
void SetCondition( int iCondition /*, bool state = true*/ );
bool HasCondition( int iCondition );
bool HasCondition( int iCondition, bool bUseIgnoreConditions );
bool HasInterruptCondition( int iCondition );
bool HasConditionsToInterruptSchedule( int nLocalScheduleID );
void ClearCondition( int iCondition );
void ClearConditions( int *pConditions, int nConditions );
void SetIgnoreConditions( int *pConditions, int nConditions );
void ClearIgnoreConditions( int *pConditions, int nConditions );
bool ConditionInterruptsCurSchedule( int iCondition );
bool ConditionInterruptsSchedule( int schedule, int iCondition );
void SetCustomInterruptCondition( int nCondition );
bool IsCustomInterruptConditionSet( int nCondition );
void ClearCustomInterruptCondition( int nCondition );
void ClearCustomInterruptConditions( void );
bool ConditionsGathered() const { return m_bConditionsGathered; }
const CAI_ScheduleBits &AccessConditionBits() const { return m_Conditions; }
CAI_ScheduleBits & AccessConditionBits() { return m_Conditions; }
private:
CAI_ScheduleBits m_Conditions;
CAI_ScheduleBits m_CustomInterruptConditions; //Bit string assembled by the schedule running, then
//modified by leaf classes to suit their needs
CAI_ScheduleBits m_ConditionsPreIgnore;
CAI_ScheduleBits m_InverseIgnoreConditions;
bool m_bForceConditionsGather;
bool m_bConditionsGathered;
public:
//-----------------------------------------------------
//
// Core mapped data structures
//
// String Registries for default AI Shared by all CBaseNPCs
// These are used only during initialization and in debug
//-----------------------------------------------------
static void InitSchedulingTables();
static CAI_GlobalScheduleNamespace *GetSchedulingSymbols() { return &gm_SchedulingSymbols; }
static CAI_ClassScheduleIdSpace &AccessClassScheduleIdSpaceDirect() { return gm_ClassScheduleIdSpace; }
virtual CAI_ClassScheduleIdSpace * GetClassScheduleIdSpace() { return &gm_ClassScheduleIdSpace; }
static int GetScheduleID (const char* schedName);
static int GetConditionID (const char* condName);
static int GetTaskID (const char* taskName);
private:
friend class CAI_SystemHook;
friend class CAI_SchedulesManager;
static bool LoadDefaultSchedules(void);
static void InitDefaultScheduleSR(void);
static void InitDefaultTaskSR(void);
static void InitDefaultConditionSR(void);
static CAI_GlobalScheduleNamespace gm_SchedulingSymbols;
static CAI_ClassScheduleIdSpace gm_ClassScheduleIdSpace;
public:
//----------------------------------------------------
// Debugging tools
//
// -----------------------------
// Debuging Fields and Methods
// -----------------------------
int m_AgentDebugOverlays;
Vector m_vecAgentDebugOverlaysPos;
const char* m_failText; // Text of why it failed
const char* m_interruptText; // Text of why schedule interrupted
CAI_Schedule* m_failedSchedule; // The schedule that failed last
CAI_Schedule* m_interuptSchedule; // The schedule that was interrupted last
int m_nDebugCurIndex; // Index used for stepping through AI
void DumpTaskTimings();
virtual int DrawDebugTextOverlays( int text_offset );
void EntityText( int text_offset, const char *text, float flDuration, int r = 255, int g = 255, int b = 255, int a = 255 );
int GetDebugOverlayFlags() {return m_AgentDebugOverlays;}
string_t GetEntityName() { return NULL_STRING; }
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
inline int CAI_Agent::IncScheduleCurTaskIndex()
{
m_ScheduleState.iTaskInterrupt = 0;
return ++m_ScheduleState.iCurTask;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
inline void CAI_Agent::ResetScheduleCurTaskIndex()
{
m_ScheduleState.iCurTask = 0;
m_ScheduleState.iTaskInterrupt = 0;
}
// ============================================================================
// Macros for introducing new schedules in sub-classes
//
// Strings registries and schedules use unique ID's for each item, but
// sub-class enumerations are non-unique, so we translate between the
// enumerations and unique ID's
// ============================================================================
#define AI_BEGIN_AGENT_( derivedClass, baseClass ) \
IMPLEMENT_AGENT(derivedClass, baseClass ) \
void derivedClass::InitCustomSchedules( void ) \
{ \
typedef derivedClass CNpc; \
typedef baseClass CAgentBase; \
const char *pszClassName = #derivedClass; \
\
CUtlVector<char *> schedulesToLoad; \
CUtlVector<AIScheduleLoadFunc_t> reqiredOthers; \
CAI_AgentNamespaceInfos scheduleIds; \
CAI_AgentNamespaceInfos taskIds; \
CAI_AgentNamespaceInfos conditionIds;
#define AI_BEGIN_AGENT( derivedClass ) \
AI_BEGIN_AGENT_( derivedClass, BaseClass )
//-----------------
#define DEFINE_SCHEDULE( id, text ) \
scheduleIds.PushBack( #id, id ); \
char * g_psz##id = \
"\n Schedule" \
"\n " #id \
text \
"\n"; \
schedulesToLoad.AddToTail( (char *)g_psz##id );
//-----------------
#define DECLARE_CONDITION( id ) \
conditionIds.PushBack( #id, id );
//-----------------
#define DECLARE_TASK( id ) \
taskIds.PushBack( #id, id );
//-----------------
// IDs are stored and then added in order due to constraints in the namespace implementation
#define AI_END_AGENT() \
\
int i; \
\
CNpc::AccessClassScheduleIdSpaceDirect().Init( pszClassName, CAgentBase::GetSchedulingSymbols(), &CAgentBase::AccessClassScheduleIdSpaceDirect() ); \
\
scheduleIds.Sort(); \
taskIds.Sort(); \
conditionIds.Sort(); \
\
for ( i = 0; i < scheduleIds.Count(); i++ ) \
{ \
ADD_CUSTOM_SCHEDULE_NAMED( CNpc, scheduleIds[i].pszName, scheduleIds[i].localId ); \
} \
\
for ( i = 0; i < taskIds.Count(); i++ ) \
{ \
ADD_CUSTOM_TASK_NAMED( CNpc, taskIds[i].pszName, taskIds[i].localId ); \
} \
\
for ( i = 0; i < conditionIds.Count(); i++ ) \
{ \
if ( AIAgentValidateConditionLimits( conditionIds[i].pszName ) ) \
{ \
ADD_CUSTOM_CONDITION_NAMED( CNpc, conditionIds[i].pszName, conditionIds[i].localId ); \
} \
} \
\
for ( i = 0; i < reqiredOthers.Count(); i++ ) \
{ \
(*reqiredOthers[i])(); \
} \
\
for ( i = 0; i < schedulesToLoad.Count(); i++ ) \
{ \
if ( CNpc::gm_SchedLoadStatus.fValid ) \
{ \
CNpc::gm_SchedLoadStatus.fValid = g_AI_AgentSchedulesManager.LoadSchedulesFromBuffer( pszClassName, schedulesToLoad[i], &AccessClassScheduleIdSpaceDirect(), GetSchedulingSymbols() ); \
} \
else \
break; \
} \
}
inline bool AIAgentValidateConditionLimits( const char *pszNewCondition )
{
int nGlobalConditions = CAI_Agent::GetSchedulingSymbols()->NumConditions();
if ( nGlobalConditions >= MAX_CONDITIONS )
{
AssertMsg2( 0, "Exceeded max number of conditions (%d), ignoring condition %s\n", MAX_CONDITIONS, pszNewCondition );
DevWarning( "Exceeded max number of conditions (%d), ignoring condition %s\n", MAX_CONDITIONS, pszNewCondition );
return false;
}
return true;
}
//-------------------------------------
struct AI_AgentNamespaceAddInfo_t
{
AI_AgentNamespaceAddInfo_t( const char *pszName, int localId )
: pszName( pszName ),
localId( localId )
{
}
const char *pszName;
int localId;
};
class CAI_AgentNamespaceInfos : public CUtlVector<AI_AgentNamespaceAddInfo_t>
{
public:
void PushBack( const char *pszName, int localId )
{
AddToTail( AI_AgentNamespaceAddInfo_t( pszName, localId ) );
}
void Sort()
{
CUtlVector<AI_AgentNamespaceAddInfo_t>::Sort( Compare );
}
private:
static int __cdecl Compare(const AI_AgentNamespaceAddInfo_t *pLeft, const AI_AgentNamespaceAddInfo_t *pRight )
{
return pLeft->localId - pRight->localId;
}
};
//-------------------------------------
// Declares the static variables that hold the string registry offset for the new subclass
// as well as the initialization in schedule load functions
struct AI_AgentSchedLoadStatus_t
{
bool fValid;
int signature;
};
// Load schedules pulled out to support stepping through with debugger
inline bool AI_DoLoadSchedules( bool (*pfnBaseLoad)(), void (*pfnInitCustomSchedules)(),
AI_AgentSchedLoadStatus_t *pLoadStatus )
{
(*pfnBaseLoad)();
if (pLoadStatus->signature != g_AI_AgentSchedulesManager.GetScheduleLoadSignature())
{
(*pfnInitCustomSchedules)();
pLoadStatus->fValid = true;
pLoadStatus->signature = g_AI_AgentSchedulesManager.GetScheduleLoadSignature();
}
return pLoadStatus->fValid;
}
//-------------------------------------
typedef bool (*AIScheduleLoadFunc_t)();
// @Note (toml 02-16-03): The following class exists to allow us to establish an anonymous friendship
// in DEFINE_AGENT. The particulars of this implementation is almost entirely
// defined by bugs in MSVC 6.0
class AgentScheduleLoadHelperImpl
{
public:
template <typename T>
static AIScheduleLoadFunc_t AccessScheduleLoadFunc(T *)
{
return (&T::LoadSchedules);
}
};
//-------------------------------------
#define DEFINE_AGENT()\
static AI_AgentSchedLoadStatus_t gm_SchedLoadStatus; \
static CAI_ClassScheduleIdSpace gm_ClassScheduleIdSpace; \
static const char * gm_pszErrorClassName;\
\
static CAI_ClassScheduleIdSpace & AccessClassScheduleIdSpaceDirect() { return gm_ClassScheduleIdSpace; } \
virtual CAI_ClassScheduleIdSpace * GetClassScheduleIdSpace() { return &gm_ClassScheduleIdSpace; } \
virtual const char * GetSchedulingErrorName() { return gm_pszErrorClassName; } \
\
static void InitCustomSchedules(void);\
\
static bool LoadSchedules(void);\
virtual bool LoadedSchedules(void); \
\
friend class AgentScheduleLoadHelperImpl; \
\
class CScheduleLoader \
{ \
public: \
CScheduleLoader(); \
} m_ScheduleLoader; \
\
friend class CScheduleLoader;
//-------------------------------------
#define IMPLEMENT_AGENT(derivedClass, baseClass)\
AI_AgentSchedLoadStatus_t derivedClass::gm_SchedLoadStatus = { true, -1 }; \
CAI_ClassScheduleIdSpace derivedClass::gm_ClassScheduleIdSpace; \
const char * derivedClass::gm_pszErrorClassName = #derivedClass; \
\
derivedClass::CScheduleLoader::CScheduleLoader()\
{ \
derivedClass::LoadSchedules(); \
} \
\
/* --------------------------------------------- */ \
/* Load schedules for this type of NPC */ \
/* --------------------------------------------- */ \
bool derivedClass::LoadSchedules(void)\
{\
return AI_DoLoadSchedules( derivedClass::baseClass::LoadSchedules, \
derivedClass::InitCustomSchedules, \
&derivedClass::gm_SchedLoadStatus ); \
}\
\
bool derivedClass::LoadedSchedules(void) \
{ \
return derivedClass::gm_SchedLoadStatus.fValid;\
}
//-------------------------------------
#define ADD_CUSTOM_SCHEDULE_NAMED(derivedClass,schedName,schedEN)\
if ( !derivedClass::AccessClassScheduleIdSpaceDirect().AddSchedule( schedName, schedEN, derivedClass::gm_pszErrorClassName ) ) return;
#define ADD_CUSTOM_SCHEDULE(derivedClass,schedEN) ADD_CUSTOM_SCHEDULE_NAMED(derivedClass,#schedEN,schedEN)
#define ADD_CUSTOM_TASK_NAMED(derivedClass,taskName,taskEN)\
if ( !derivedClass::AccessClassScheduleIdSpaceDirect().AddTask( taskName, taskEN, derivedClass::gm_pszErrorClassName ) ) return;
#define ADD_CUSTOM_TASK(derivedClass,taskEN) ADD_CUSTOM_TASK_NAMED(derivedClass,#taskEN,taskEN)
#define ADD_CUSTOM_CONDITION_NAMED(derivedClass,condName,condEN)\
if ( !derivedClass::AccessClassScheduleIdSpaceDirect().AddCondition( condName, condEN, derivedClass::gm_pszErrorClassName ) ) return;
#define ADD_CUSTOM_CONDITION(derivedClass,condEN) ADD_CUSTOM_CONDITION_NAMED(derivedClass,#condEN,condEN)
#endif // AI_AGENT_H
File diff suppressed because it is too large Load Diff
+343
View File
@@ -0,0 +1,343 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Hooks and classes for the support of humanoid NPCs with
// groovy facial animation capabilities, aka, "Actors"
//
//=============================================================================//
#ifndef AI_BASEACTOR_H
#define AI_BASEACTOR_H
#include "ai_basehumanoid.h"
#include "ai_speech.h"
#include "AI_Interest_Target.h"
#include <limits.h>
#if defined( _WIN32 )
#pragma once
#endif
//-----------------------------------------------------------------------------
// CAI_BaseActor
//
// Purpose: The base class for all head/body/eye expressive NPCS.
//
//-----------------------------------------------------------------------------
enum PoseParameter_t { POSE_END=INT_MAX };
enum FlexWeight_t { FLEX_END=INT_MAX };
class CInfoRemarkable;
struct AILookTargetArgs_t
{
EHANDLE hTarget;
Vector vTarget;
float flDuration;
float flInfluence;
float flRamp;
bool bExcludePlayers;
CAI_InterestTarget *pQueue;
};
class CAI_BaseActor : public CAI_ExpresserHost<CAI_BaseHumanoid>
{
DECLARE_CLASS( CAI_BaseActor, CAI_ExpresserHost<CAI_BaseHumanoid> );
//friend CPoseParameter;
//friend CFlexWeight;
#pragma region PoseParameter and FlexWeight get/set
public:
void Init( PoseParameter_t &index, const char *szName ) { index = (PoseParameter_t)LookupPoseParameter( szName ); };
void Set( PoseParameter_t index, float flValue ) { SetPoseParameter( (int)index, flValue ); }
float Get( PoseParameter_t index ) { return GetPoseParameter( (int)index ); }
float ClampWithBias( PoseParameter_t index, float value, float base );
// Note, you must add all names to this static function in order for Init to work
static bool IsServerSideFlexController( char const *szName );
void Init( FlexWeight_t &index, const char *szName )
{
// Make this fatal!!!
if ( !IsServerSideFlexController( szName ) )
{
Error( "You forgot to add flex controller %s to list in CAI_BaseActor::IsServerSideFlexController().", szName );
}
index = (FlexWeight_t)FindFlexController( szName );
}
void Set( FlexWeight_t index, float flValue ) { SetFlexWeight( (LocalFlexController_t)index, flValue ); }
float Get( FlexWeight_t index ) { return GetFlexWeight( (LocalFlexController_t)index ); }
#pragma endregion
public:
CAI_BaseActor()
: m_fLatchedPositions( 0 ),
m_latchedEyeOrigin( vec3_origin ),
m_latchedEyeDirection( vec3_origin ),
m_latchedHeadDirection( vec3_origin ),
m_flBlinktime( 0 ),
m_hLookTarget( INVALID_EHANDLE ),
m_iszExpressionScene( NULL_STRING ),
m_iszIdleExpression( NULL_STRING ),
m_iszAlertExpression( NULL_STRING ),
m_iszCombatExpression( NULL_STRING ),
m_iszDeathExpression( NULL_STRING ),
m_iszExpressionOverride( NULL_STRING ),
m_bRemarkablePolling( false )
{
memset( m_flextarget, 0, 64 * sizeof( m_flextarget[0] ) );
}
~CAI_BaseActor()
{
delete m_pExpresser;
}
virtual void StudioFrameAdvance();
virtual void Precache();
virtual void SetModel( const char *szModelName );
virtual bool StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget );
virtual bool ProcessSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event );
virtual bool ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled );
virtual bool CheckSceneEventCompletion( CSceneEventInfo *info, float currenttime, CChoreoScene *scene, CChoreoEvent *event );
Vector EyePosition( );
virtual Vector HeadDirection2D( void );
virtual Vector HeadDirection3D( void );
virtual Vector EyeDirection2D( void );
virtual Vector EyeDirection3D( void );
CBaseEntity *GetLooktarget() { return m_hLookTarget.Get(); }
virtual void OnNewLookTarget() {};
// CBaseFlex
virtual void SetViewtarget( const Vector &viewtarget );
// CAI_BaseNPC
virtual float PickLookTarget( bool bExcludePlayers = false, float minTime = 1.5, float maxTime = 2.5 );
virtual float PickLookTarget( CAI_InterestTarget &queue, bool bExcludePlayers = false, float minTime = 1.5, float maxTime = 2.5 );
virtual bool PickTacticalLookTarget( AILookTargetArgs_t *pArgs );
virtual bool PickRandomLookTarget( AILookTargetArgs_t *pArgs );
virtual void MakeRandomLookTarget( AILookTargetArgs_t *pArgs, float minTime, float maxTime );
virtual bool HasActiveLookTargets( void );
virtual void OnSelectedLookTarget( AILookTargetArgs_t *pArgs ) { return; }
virtual void ClearLookTarget( CBaseEntity *pTarget );
virtual void ExpireCurrentRandomLookTarget() { m_flNextRandomLookTime = gpGlobals->curtime - 0.1f; }
virtual void AddLookTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp = 0.0 );
virtual void AddLookTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp = 0.0 );
virtual void MaintainLookTargets( float flInterval );
virtual bool ValidEyeTarget(const Vector &lookTargetPos);
virtual float HeadTargetValidity(const Vector &lookTargetPos);
virtual void StartTaskRangeAttack1( const Task_t *pTask );
virtual void SetHeadDirection( const Vector &vTargetPos, float flInterval );
void UpdateBodyControl( void );
void UpdateHeadControl( const Vector &vHeadTarget, float flHeadInfluence );
virtual float GetHeadDebounce( void ) { return 0.3; } // how much of previous head turn to use
virtual bool ShouldBruteForceFailedNav() { return true; }
virtual void GatherConditions( void );
bool ValidHeadTarget( const Vector &lookTargetDir, float flDist );
void AccumulateIdealYaw( float flYaw, float flIntensity );
bool SetAccumulatedYawAndUpdate( void );
float m_flAccumYawDelta;
float m_flAccumYawScale;
//---------------------------------
virtual void OnStateChange( NPC_STATE OldState, NPC_STATE NewState );
//-- Code for responding to INFO_REMARKABLES --
#pragma region Info_Remarkable Code
// INFO_REMARKABLEs are objects in the world for which AIs poll and
// potentially say context-sensitive things.
// The code is here because for the moment only AI_BaseActors do this.
// However any other ExpresserHost theoretically could; if you want this
// on, say, a player, we'd need to create some new common base class or
// some such.
// called from GatherConditions() for now because can't think of a better
// place to poll it from. Returns true if speaking was tried.
bool UpdateRemarkableSpeech() RESTRICT;
inline void EnableRemarkables( bool bEnabled ) { m_bRemarkablePolling = bEnabled; }
protected:
/// true iff the character is allowed to poll for remarkables at all (eg,
/// rate limiting). You can make it virtual if you need to.
bool CanPollRemarkables();
/// Test to see if a particular remarkable can be commented upon.
virtual bool TestRemarkingUpon( CInfoRemarkable * pRemarkable );
public:
#pragma endregion
//---------------------------------
virtual void PlayExpressionForState( NPC_STATE state );
virtual const char *SelectRandomExpressionForState( NPC_STATE state );
float SetExpression( const char * );
void ClearExpression();
const char * GetExpression();
enum
{
SCENE_AI_BLINK = 1,
SCENE_AI_HOLSTER,
SCENE_AI_UNHOLSTER,
SCENE_AI_AIM,
SCENE_AI_RANDOMLOOK,
SCENE_AI_RANDOMFACEFLEX,
SCENE_AI_RANDOMHEADFLEX,
SCENE_AI_IGNORECOLLISION,
SCENE_AI_DISABLEAI
};
public:
//---------------------------------
virtual void Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity, bool bUseSlowHighAccuracyContacts = true );
void InvalidateBoneCache( void );
DECLARE_DATADESC();
private:
enum
{
HUMANOID_LATCHED_EYE = 0x0001,
HUMANOID_LATCHED_HEAD = 0x0002,
HUMANOID_LATCHED_ALL = 0x0003,
};
//---------------------------------
void UpdateLatchedValues( void );
// Input handlers.
void InputSetExpressionOverride( inputdata_t &inputdata );
//---------------------------------
int m_fLatchedPositions;
Vector m_latchedEyeOrigin;
Vector m_latchedEyeDirection; // direction eyes are looking
Vector m_latchedHeadDirection; // direction head is aiming
void ClearHeadAdjustment( void );
Vector m_goalHeadDirection;
float m_goalHeadInfluence;
//---------------------------------
float m_goalSpineYaw;
float m_goalBodyYaw;
Vector m_goalHeadCorrection;
//---------------------------------
float m_flBlinktime;
EHANDLE m_hLookTarget;
CAI_InterestTarget m_lookQueue;
CAI_InterestTarget m_syntheticLookQueue;
CAI_InterestTarget m_randomLookQueue;
float m_flNextRandomLookTime; // FIXME: move to scene
//---------------------------------
string_t m_iszExpressionScene;
EHANDLE m_hExpressionSceneEnt;
float m_flNextRandomExpressionTime;
string_t m_iszExpressionOverride;
protected:
string_t m_iszIdleExpression;
string_t m_iszAlertExpression;
string_t m_iszCombatExpression;
string_t m_iszDeathExpression;
bool m_bRemarkablePolling;
float m_fNextIdleVocalizeTime; ///< Not a CoundownTimer because it doesn't need to be networked
float m_fNextRemarkPollTime; ///< we only poll for TLK_REMARK once per second or so
#pragma region PoseParameters and FlexWeights
private:
//---------------------------------
//PoseParameter_t m_ParameterBodyTransY; // "body_trans_Y"
//PoseParameter_t m_ParameterBodyTransX; // "body_trans_X"
//PoseParameter_t m_ParameterBodyLift; // "body_lift"
PoseParameter_t m_ParameterBodyYaw; // "body_yaw"
//PoseParameter_t m_ParameterBodyPitch; // "body_pitch"
//PoseParameter_t m_ParameterBodyRoll; // "body_roll"
PoseParameter_t m_ParameterSpineYaw; // "spine_yaw"
//PoseParameter_t m_ParameterSpinePitch; // "spine_pitch"
//PoseParameter_t m_ParameterSpineRoll; // "spine_roll"
PoseParameter_t m_ParameterNeckTrans; // "neck_trans"
PoseParameter_t m_ParameterHeadYaw; // "head_yaw"
PoseParameter_t m_ParameterHeadPitch; // "head_pitch"
PoseParameter_t m_ParameterHeadRoll; // "head_roll"
//FlexWeight_t m_FlexweightMoveRightLeft; // "move_rightleft"
//FlexWeight_t m_FlexweightMoveForwardBack;// "move_forwardback"
//FlexWeight_t m_FlexweightMoveUpDown; // "move_updown"
FlexWeight_t m_FlexweightBodyRightLeft; // "body_rightleft"
//FlexWeight_t m_FlexweightBodyUpDown; // "body_updown"
//FlexWeight_t m_FlexweightBodyTilt; // "body_tilt"
FlexWeight_t m_FlexweightChestRightLeft; // "chest_rightleft"
//FlexWeight_t m_FlexweightChestUpDown; // "chest_updown"
//FlexWeight_t m_FlexweightChestTilt; // "chest_tilt"
FlexWeight_t m_FlexweightHeadForwardBack;// "head_forwardback"
FlexWeight_t m_FlexweightHeadRightLeft; // "head_rightleft"
FlexWeight_t m_FlexweightHeadUpDown; // "head_updown"
FlexWeight_t m_FlexweightHeadTilt; // "head_tilt"
PoseParameter_t m_ParameterGestureHeight; // "gesture_height"
PoseParameter_t m_ParameterGestureWidth; // "gesture_width"
FlexWeight_t m_FlexweightGestureUpDown; // "gesture_updown"
FlexWeight_t m_FlexweightGestureRightLeft; // "gesture_rightleft"
#pragma endregion Cached indices
private:
//---------------------------------
bool RandomFaceFlex( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event );
bool RandomHeadFlex( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event );
float m_flextarget[64];
public:
virtual bool UseSemaphore( void );
protected:
bool m_bDontUseSemaphore;
public:
//---------------------------------
//
// Speech support
//
virtual CAI_Expresser *GetExpresser();
protected:
bool CreateComponents();
virtual CAI_Expresser *CreateExpresser();
private:
//---------------------------------
CAI_Expresser *m_pExpresser;
};
//-----------------------------------------------------------------------------
#endif // AI_BASEACTOR_H
+335
View File
@@ -0,0 +1,335 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "BasePropDoor.h"
#include "ai_basehumanoid.h"
#include "ai_blended_movement.h"
#include "ai_navigator.h"
#include "ai_memory.h"
#ifdef HL2_DLL
#include "ai_interactions.h"
#endif
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Purpose: This is a generic function (to be implemented by sub-classes) to
// handle specific interactions between different types of characters
// (For example the barnacle grabbing an NPC)
// Input : Constant for the type of interaction
// Output : true - if sub-class has a response for the interaction
// false - if sub-class has no response
//-----------------------------------------------------------------------------
bool CAI_BaseHumanoid::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
{
#if defined( HL2_DLL )
// Annoying to ifdef this out. Copy it into all the HL2 specific humanoid NPC's instead?
if ( interactionType == g_interactionBarnacleVictimDangle )
{
// Force choosing of a new schedule
ClearSchedule( "Grabbed by a barnacle" );
return true;
}
else if ( interactionType == g_interactionBarnacleVictimReleased )
{
// Destroy the entity, the barnacle is going to use the ragdoll that it is releasing
// as the corpse.
UTIL_Remove( this );
return true;
}
#endif
return BaseClass::HandleInteraction( interactionType, data, sourceEnt);
}
//-----------------------------------------------------------------------------
// Purpose: check ammo
//-----------------------------------------------------------------------------
void CAI_BaseHumanoid::CheckAmmo( void )
{
BaseClass::CheckAmmo();
// FIXME: put into GatherConditions()?
// FIXME: why isn't this a baseclass function?
if (!GetActiveWeapon())
return;
// Don't do this while holstering / unholstering
if ( IsWeaponStateChanging() )
return;
if (GetActiveWeapon()->UsesPrimaryAmmo())
{
if (!GetActiveWeapon()->HasPrimaryAmmo() )
{
SetCondition(COND_NO_PRIMARY_AMMO);
}
else if (GetActiveWeapon()->UsesClipsForAmmo1() && GetActiveWeapon()->Clip1() < (GetActiveWeapon()->GetMaxClip1() / 4 + 1))
{
// don't check for low ammo if you're near the max range of the weapon
SetCondition(COND_LOW_PRIMARY_AMMO);
}
}
if (!GetActiveWeapon()->HasSecondaryAmmo() )
{
if ( GetActiveWeapon()->UsesClipsForAmmo2() )
{
SetCondition(COND_NO_SECONDARY_AMMO);
}
}
}
//-----------------------------------------------------------------------------
// TASK_RANGE_ATTACK1
//-----------------------------------------------------------------------------
void CAI_BaseHumanoid::BuildScheduleTestBits( )
{
BaseClass::BuildScheduleTestBits();
if ( CapabilitiesGet() & bits_CAP_USE_SHOT_REGULATOR )
{
if ( GetShotRegulator()->IsInRestInterval() )
{
ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 );
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
static bool IsSmall( CBaseEntity *pBlocker )
{
CCollisionProperty *pCollisionProp = pBlocker->CollisionProp();
int nSmaller = 0;
Vector vecSize = pCollisionProp->OBBMaxs() - pCollisionProp->OBBMins();
for ( int i = 0; i < 3; i++ )
{
if ( vecSize[i] >= 42 )
return false;
if ( vecSize[i] <= 30 )
{
nSmaller++;
}
}
return ( nSmaller >= 2 );
}
bool CAI_BaseHumanoid::OnMoveBlocked( AIMoveResult_t *pResult )
{
if ( *pResult != AIMR_BLOCKED_NPC && GetNavigator()->GetBlockingEntity() && !GetNavigator()->GetBlockingEntity()->IsNPC() )
{
CBaseEntity *pBlocker = GetNavigator()->GetBlockingEntity();
float massBonus = ( IsNavigationUrgent() ) ? 40.0 : 0;
if ( pBlocker->GetMoveType() == MOVETYPE_VPHYSICS &&
pBlocker != GetGroundEntity() &&
!pBlocker->IsNavIgnored() &&
!dynamic_cast<CBasePropDoor *>(pBlocker) &&
pBlocker->VPhysicsGetObject() &&
pBlocker->VPhysicsGetObject()->IsMoveable() &&
( pBlocker->VPhysicsGetObject()->GetMass() <= 35.0 + massBonus + 0.1 ||
( pBlocker->VPhysicsGetObject()->GetMass() <= 50.0 + massBonus + 0.1 && IsSmall( pBlocker ) ) ) )
{
DbgNavMsg1( this, "Setting ignore on object %s", pBlocker->GetDebugName() );
pBlocker->SetNavIgnore( 2.5 );
}
#if 0
else
{
CPhysicsProp *pProp = dynamic_cast<CPhysicsProp*>( pBlocker );
if ( pProp && pProp->GetHealth() && pProp->GetExplosiveDamage() == 0.0 && GetActiveWeapon() && !GetActiveWeapon()->ClassMatches( "weapon_rpg" ) )
{
Msg( "!\n" );
// Destroy!
}
}
#endif
}
return BaseClass::OnMoveBlocked( pResult );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
#define SNEAK_ATTACK_DIST 360.0f // 30 feet
void CAI_BaseHumanoid::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr )
{
bool bSneakAttacked = false;
if( ptr->hitgroup == HITGROUP_HEAD )
{
if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() && info.GetAttacker() != GetEnemy() && !IsInAScript() )
{
// Shot in the head by a player I've never seen. In this case the player
// has gotten the drop on this enemy and such an attack is always lethal (at close range)
bSneakAttacked = true;
AIEnemiesIter_t iter;
for( AI_EnemyInfo_t *pMemory = GetEnemies()->GetFirst(&iter); pMemory != NULL; pMemory = GetEnemies()->GetNext(&iter) )
{
if ( pMemory->hEnemy == info.GetAttacker() )
{
bSneakAttacked = false;
break;
}
}
float flDist;
flDist = (info.GetAttacker()->GetAbsOrigin() - GetAbsOrigin()).Length();
if( flDist > SNEAK_ATTACK_DIST )
{
bSneakAttacked = false;
}
}
}
if( bSneakAttacked )
{
CTakeDamageInfo newInfo = info;
newInfo.SetDamage( GetHealth() );
BaseClass::TraceAttack( newInfo, vecDir, ptr );
return;
}
BaseClass::TraceAttack( info, vecDir, ptr );
}
//-----------------------------------------------------------------------------
// TASK_RANGE_ATTACK1
//-----------------------------------------------------------------------------
void CAI_BaseHumanoid::StartTaskRangeAttack1( const Task_t *pTask )
{
if ( ( CapabilitiesGet() & bits_CAP_USE_SHOT_REGULATOR ) == 0 )
{
BaseClass::StartTask( pTask );
return;
}
// Can't shoot if we're in the rest interval; fail the schedule
if ( GetShotRegulator()->IsInRestInterval() )
{
TaskFail( "Shot regulator in rest interval" );
return;
}
if ( GetShotRegulator()->ShouldShoot() )
{
OnRangeAttack1();
ResetIdealActivity( ACT_RANGE_ATTACK1 );
}
else
{
// This can happen if we start while in the middle of a burst
// which shouldn't happen, but given the chaotic nature of our AI system,
// does occasionally happen.
ResetIdealActivity( ACT_IDLE_ANGRY );
}
}
//-----------------------------------------------------------------------------
// Starting Tasks
//-----------------------------------------------------------------------------
void CAI_BaseHumanoid::StartTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_RANGE_ATTACK1:
StartTaskRangeAttack1( pTask );
break;
default:
BaseClass::StartTask( pTask );
}
}
//-----------------------------------------------------------------------------
// TASK_RANGE_ATTACK1 / TASK_RANGE_ATTACK2 / etc.
//-----------------------------------------------------------------------------
void CAI_BaseHumanoid::RunTaskRangeAttack1( const Task_t *pTask )
{
if ( ( CapabilitiesGet() & bits_CAP_USE_SHOT_REGULATOR ) == 0 )
{
BaseClass::RunTask( pTask );
return;
}
AutoMovement( );
Vector vecEnemyLKP = GetEnemyLKP();
// If our enemy was killed, but I'm not done animating, the last known position comes
// back as the origin and makes the me face the world origin if my attack schedule
// doesn't break when my enemy dies. (sjb)
if( vecEnemyLKP != vec3_origin )
{
if ( ( pTask->iTask == TASK_RANGE_ATTACK1 || pTask->iTask == TASK_RELOAD ) &&
( CapabilitiesGet() & bits_CAP_AIM_GUN ) &&
FInAimCone( vecEnemyLKP ) )
{
// Arms will aim, so leave body yaw as is
GetMotor()->SetIdealYawAndUpdate( GetMotor()->GetIdealYaw(), AI_KEEP_YAW_SPEED );
}
else
{
GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP, AI_KEEP_YAW_SPEED );
}
}
if ( IsActivityFinished() )
{
if ( !GetEnemy() || !GetEnemy()->IsAlive() )
{
TaskComplete();
return;
}
if ( !GetShotRegulator()->IsInRestInterval() )
{
if ( GetShotRegulator()->ShouldShoot() )
{
OnRangeAttack1();
ResetIdealActivity( ACT_RANGE_ATTACK1 );
}
return;
}
TaskComplete();
}
}
//-----------------------------------------------------------------------------
// Running Tasks
//-----------------------------------------------------------------------------
void CAI_BaseHumanoid::RunTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_RANGE_ATTACK1:
RunTaskRangeAttack1( pTask );
break;
default:
BaseClass::RunTask( pTask );
}
}
+50
View File
@@ -0,0 +1,50 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef AI_BASEHUMANOID_H
#define AI_BASEHUMANOID_H
#include "ai_behavior.h"
#include "ai_blended_movement.h"
//-----------------------------------------------------------------------------
// CLASS: CAI_BaseHumanoid
//-----------------------------------------------------------------------------
typedef CAI_BlendingHost< CAI_BehaviorHost<CAI_BaseNPC> > CAI_BaseHumanoidBase;
class CAI_BaseHumanoid : public CAI_BaseHumanoidBase
{
DECLARE_CLASS( CAI_BaseHumanoid, CAI_BaseHumanoidBase );
public:
bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt);
// Tasks
virtual void StartTask( const Task_t *pTask );
virtual void RunTask( const Task_t *pTask );
virtual void BuildScheduleTestBits( );
// Navigation
bool OnMoveBlocked( AIMoveResult_t *pResult );
// Damage
void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr );
// Various start tasks
virtual void StartTaskRangeAttack1( const Task_t *pTask );
// Various run tasks
virtual void RunTaskRangeAttack1( const Task_t *pTask );
// Purpose: check ammo
virtual void CheckAmmo( void );
};
//-----------------------------------------------------------------------------
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+263
View File
@@ -0,0 +1,263 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Base class for many flying NPCs
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "ai_basenpc_flyer.h"
#include "ai_route.h"
#include "ai_navigator.h"
#include "ai_motor.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
BEGIN_DATADESC( CAI_BaseFlyingBot )
DEFINE_FIELD( m_vCurrentVelocity, FIELD_VECTOR),
DEFINE_FIELD( m_vCurrentAngularVelocity, FIELD_VECTOR ),
DEFINE_FIELD( m_vCurrentBanking, FIELD_VECTOR),
DEFINE_FIELD( m_vNoiseMod, FIELD_VECTOR),
DEFINE_FIELD( m_fHeadYaw, FIELD_FLOAT),
DEFINE_FIELD( m_vLastPatrolDir, FIELD_VECTOR),
END_DATADESC()
//------------------------------------------------------------------------------
// Purpose : Override to return correct velocity
// Input :
// Output :
//------------------------------------------------------------------------------
void CAI_BaseFlyingBot::GetVelocity(Vector *vVelocity, AngularImpulse *vAngVelocity)
{
if (vVelocity != NULL)
{
VectorCopy(m_vCurrentVelocity,*vVelocity);
}
if (vAngVelocity != NULL)
{
QAngle tmp = GetLocalAngularVelocity();
QAngleToAngularImpulse( tmp, *vAngVelocity );
}
}
//-----------------------------------------------------------------------------
// Purpose: Turn head yaw into facing direction
// Input :
// Output :
//-----------------------------------------------------------------------------
QAngle CAI_BaseFlyingBot::BodyAngles()
{
return QAngle(0,m_fHeadYaw,0);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CAI_BaseFlyingBot::TurnHeadToTarget(float flInterval, const Vector &MoveTarget )
{
float flDestYaw = VecToYaw( MoveTarget - GetLocalOrigin() );
float newYaw = AI_ClampYaw( GetHeadTurnRate() * 10.0f, m_fHeadYaw, flDestYaw, gpGlobals->curtime - GetLastThink() );
if ( newYaw != m_fHeadYaw )
{
m_fHeadYaw = newYaw;
}
// Set us to face that way
SetBoneController( 0, m_fHeadYaw );
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
float CAI_BaseFlyingBot::MinGroundDist(void)
{
return 0;
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
Vector CAI_BaseFlyingBot::VelocityToAvoidObstacles(float flInterval)
{
// --------------------------------
// Avoid banging into stuff
// --------------------------------
trace_t tr;
Vector vTravelDir = m_vCurrentVelocity*flInterval;
Vector endPos = GetAbsOrigin() + vTravelDir;
AI_TraceEntity( this, GetAbsOrigin(), endPos, GetAITraceMask()|CONTENTS_WATER, &tr );
if (tr.fraction != 1.0)
{
// Bounce off in normal
Vector vBounce = tr.plane.normal * 0.5 * m_vCurrentVelocity.Length();
return (vBounce);
}
// --------------------------------
// Try to remain above the ground.
// --------------------------------
float flMinGroundDist = MinGroundDist();
AI_TraceLine(GetAbsOrigin(), GetAbsOrigin() + Vector(0, 0, -flMinGroundDist),
GetAITraceMask_BrushOnly()|CONTENTS_WATER, this, COLLISION_GROUP_NONE, &tr);
if (tr.fraction < 1)
{
// Clamp veloctiy
if (tr.fraction < 0.1)
{
tr.fraction = 0.1;
}
return Vector(0, 0, 50/tr.fraction);
}
return vec3_origin;
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
void CAI_BaseFlyingBot::StartTask( const Task_t *pTask )
{
switch (pTask->iTask)
{
// Skip as done via bone controller
case TASK_FACE_ENEMY:
{
TaskComplete();
break;
}
// Activity is just idle (have no run)
case TASK_RUN_PATH:
{
GetNavigator()->SetMovementActivity(ACT_IDLE);
TaskComplete();
break;
}
// Don't check for run/walk activity
case TASK_SCRIPT_RUN_TO_TARGET:
case TASK_SCRIPT_WALK_TO_TARGET:
{
if (GetTarget() == NULL)
{
TaskFail(FAIL_NO_TARGET);
}
else
{
if (!GetNavigator()->SetGoal( GOALTYPE_TARGETENT ) )
{
TaskFail(FAIL_NO_ROUTE);
GetNavigator()->ClearGoal();
}
}
TaskComplete();
break;
}
// Override to get more to get a directional path
case TASK_GET_PATH_TO_RANDOM_NODE:
{
if ( GetNavigator()->SetRandomGoal( pTask->flTaskData, m_vLastPatrolDir ) )
TaskComplete();
else
TaskFail(FAIL_NO_REACHABLE_NODE);
break;
}
default:
{
BaseClass::StartTask(pTask);
}
}
}
//------------------------------------------------------------------------------
void CAI_BaseFlyingBot::MoveToTarget(float flInterval, const Vector &MoveTarget)
{
Assert(0); // This must be overridden in the leaf classes
}
//------------------------------------------------------------------------------
AI_NavPathProgress_t CAI_BaseFlyingBot::ProgressFlyPath(
float flInterval,
const CBaseEntity *pNewTarget,
unsigned collisionMask,
bool bNewTrySimplify,
float strictPointTolerance)
{
AI_ProgressFlyPathParams_t params( collisionMask, strictPointTolerance );
params.SetCurrent( pNewTarget, bNewTrySimplify );
AI_NavPathProgress_t progress = GetNavigator()->ProgressFlyPath( params );
switch ( progress )
{
case AINPP_NO_CHANGE:
case AINPP_ADVANCED:
{
MoveToTarget(flInterval, GetNavigator()->GetCurWaypointPos());
break;
}
case AINPP_COMPLETE:
{
TaskMovementComplete();
break;
}
case AINPP_BLOCKED: // function is not supposed to test blocking, just simple path progression
default:
{
AssertMsg( 0, ( "Unexpected result" ) );
break;
}
}
return progress;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTarget -
// &chasePosition -
//-----------------------------------------------------------------------------
void CAI_BaseFlyingBot::TranslateNavGoal( CBaseEntity *pTarget, Vector &chasePosition )
{
Assert( pTarget != NULL );
if ( pTarget == NULL )
{
chasePosition = vec3_origin;
return;
}
// Chase their eyes
chasePosition = pTarget->GetAbsOrigin() + pTarget->GetViewOffset();
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
CAI_BaseFlyingBot::CAI_BaseFlyingBot()
{
#ifdef _DEBUG
m_vCurrentVelocity.Init();
m_vCurrentBanking.Init();
m_vLastPatrolDir.Init();
#endif
}
+132
View File
@@ -0,0 +1,132 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef AI_BASENPC_FLYER_H
#define AI_BASENPC_FLYER_H
#ifdef _WIN32
#pragma once
#endif
#include "ai_basenpc.h"
#include "ai_navigator.h"
//-----------------------------------------------------------------------------
// The combot.
//-----------------------------------------------------------------------------
abstract_class CAI_BaseFlyingBot : public CAI_BaseNPC
{
DECLARE_CLASS( CAI_BaseFlyingBot, CAI_BaseNPC );
public:
DECLARE_DATADESC();
void StartTask( const Task_t *pTask );
void GetVelocity(Vector *vVelocity, AngularImpulse *vAngVelocity);
virtual QAngle BodyAngles();
protected:
CAI_BaseFlyingBot();
Vector VelocityToAvoidObstacles(float flInterval);
virtual float MinGroundDist(void);
void TurnHeadToTarget( float flInterval, const Vector &moveTarget );
void MoveInDirection( float flInterval, const Vector &targetDir,
float accelXY, float accelZ, float decay)
{
decay = ExponentialDecay( decay, 1.0, flInterval );
accelXY *= flInterval;
accelZ *= flInterval;
m_vCurrentVelocity.x = ( decay * m_vCurrentVelocity.x + accelXY * targetDir.x );
m_vCurrentVelocity.y = ( decay * m_vCurrentVelocity.y + accelXY * targetDir.y );
m_vCurrentVelocity.z = ( decay * m_vCurrentVelocity.z + accelZ * targetDir.z );
}
void MoveToLocation( float flInterval, const Vector &target,
float accelXY, float accelZ, float decay)
{
Vector targetDir = target - GetLocalOrigin();
VectorNormalize(targetDir);
MoveInDirection(flInterval, targetDir, accelXY, accelZ, decay);
}
void Decelerate( float flInterval, float decay )
{
decay *= flInterval;
m_vCurrentVelocity.x = (decay * m_vCurrentVelocity.x);
m_vCurrentVelocity.y = (decay * m_vCurrentVelocity.y);
m_vCurrentVelocity.z = (decay * m_vCurrentVelocity.z);
}
void AddNoiseToVelocity( float noiseScale = 1.0 )
{
if( m_vNoiseMod.x )
{
m_vCurrentVelocity.x += noiseScale*sin(m_vNoiseMod.x * gpGlobals->curtime + m_vNoiseMod.x);
}
if( m_vNoiseMod.y )
{
m_vCurrentVelocity.y += noiseScale*cos(m_vNoiseMod.y * gpGlobals->curtime + m_vNoiseMod.y);
}
if( m_vNoiseMod.z )
{
m_vCurrentVelocity.z -= noiseScale*cos(m_vNoiseMod.z * gpGlobals->curtime + m_vNoiseMod.z);
}
}
void LimitSpeed( float zLimit, float maxSpeed = -1 )
{
if ( maxSpeed == -1 )
maxSpeed = m_flSpeed;
if (m_vCurrentVelocity.Length() > maxSpeed)
{
VectorNormalize(m_vCurrentVelocity);
m_vCurrentVelocity *= maxSpeed;
}
// Limit fall speed
if (zLimit > 0 && m_vCurrentVelocity.z < -zLimit)
{
m_vCurrentVelocity.z = -zLimit;
}
}
AI_NavPathProgress_t ProgressFlyPath( float flInterval,
const CBaseEntity *pNewTarget,
unsigned collisionMask,
bool bNewTrySimplify = true,
float strictPointTolerance = 32.0 );
virtual float GetHeadTurnRate( void ) { return 15.0f; } // Degrees per second
const Vector &GetCurrentVelocity() const { return m_vCurrentVelocity; }
void SetCurrentVelocity(const Vector &vNewVel) { m_vCurrentVelocity = vNewVel; }
const Vector &GetNoiseMod() const { return m_vNoiseMod; }
void SetNoiseMod( float x, float y, float z ) { m_vNoiseMod.Init( x, y, z ); }
void SetNoiseMod( const Vector &noise ) { m_vNoiseMod = noise; }
virtual void MoveToTarget(float flInterval, const Vector &MoveTarget) = 0;
void TranslateNavGoal( CBaseEntity *pTarget, Vector &chasePosition );
// -------------------------------
// Movement vars
// -------------------------------
Vector m_vCurrentVelocity;
Vector m_vCurrentAngularVelocity;
Vector m_vCurrentBanking;
Vector m_vNoiseMod;
float m_fHeadYaw;
Vector m_vLastPatrolDir;
};
#endif // AI_BASENPC_FLYER_H

Some files were not shown because too many files have changed in this diff Show More