initial
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 ¶ms )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
@@ -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 ¶ms );
|
||||
|
||||
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
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 ¶ms ); // 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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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, ¢er, &halfWidth );
|
||||
|
||||
*crossPos = center;
|
||||
}
|
||||
|
||||
void NotifyVictim( INextBot *me, CBaseEntity *victim );
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // _NEXT_BOT_CHASE_PATH_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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" );
|
||||
|
||||
@@ -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_
|
||||
@@ -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 )
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 ) );
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user