519 lines
11 KiB
C++
519 lines
11 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: The Halflife Cycler NPCs
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "ai_basenpc.h"
|
|
#include "ai_motor.h"
|
|
#include "basecombatweapon.h"
|
|
#include "animation.h"
|
|
#include "vstdlib/random.h"
|
|
#include "h_cycler.h"
|
|
#include "Sprite.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define FCYCLER_NOTSOLID 0x0001
|
|
|
|
extern int g_sModelIndexSmoke; // (in combatweapon.cpp) holds the index for the smoke cloud
|
|
|
|
BEGIN_DATADESC( CCycler )
|
|
|
|
// Fields
|
|
DEFINE_FIELD( m_animate, FIELD_INTEGER ),
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetSequence", InputSetSequence ),
|
|
|
|
END_DATADESC()
|
|
|
|
//
|
|
// we should get rid of all the other cyclers and replace them with this.
|
|
//
|
|
class CGenericCycler : public CCycler
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CGenericCycler, CCycler );
|
|
|
|
void Spawn()
|
|
{
|
|
GenericCyclerSpawn( (char *)STRING( GetModelName() ), Vector(-16, -16, 0), Vector(16, 16, 72) );
|
|
}
|
|
};
|
|
LINK_ENTITY_TO_CLASS( cycler, CGenericCycler );
|
|
LINK_ENTITY_TO_CLASS( model_studio, CGenericCycler ); // For now model_studios build as cyclers.
|
|
|
|
|
|
// Cycler member functions
|
|
|
|
void CCycler::GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax)
|
|
{
|
|
if (!szModel || !*szModel)
|
|
{
|
|
Warning( "cycler at %.0f %.0f %0.f missing modelname\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
|
|
UTIL_Remove( this );
|
|
return;
|
|
}
|
|
|
|
Precache();
|
|
|
|
SetModel( szModel );
|
|
|
|
m_bloodColor = DONT_BLEED;
|
|
|
|
CCycler::Spawn( );
|
|
|
|
UTIL_SetSize(this, vecMin, vecMax);
|
|
}
|
|
|
|
|
|
void CCycler::Precache()
|
|
{
|
|
PrecacheModel( (const char *)STRING( GetModelName() ) );
|
|
}
|
|
|
|
|
|
void CCycler::Spawn( )
|
|
{
|
|
InitBoneControllers();
|
|
|
|
SetSolid( SOLID_BBOX );
|
|
if ( m_spawnflags & FCYCLER_NOTSOLID )
|
|
{
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
}
|
|
else
|
|
{
|
|
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
|
}
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
m_takedamage = DAMAGE_YES;
|
|
m_iHealth = 80000;// no cycler should die
|
|
GetMotor()->SetIdealYaw( GetLocalAngles().y );
|
|
GetMotor()->SnapYaw();
|
|
|
|
m_flPlaybackRate = 1.0;
|
|
m_flGroundSpeed = 0;
|
|
|
|
SetNextThink( gpGlobals->curtime + 1.0f );
|
|
|
|
ResetSequenceInfo( );
|
|
|
|
if (GetSequence() != 0 || m_flCycle != 0)
|
|
{
|
|
#ifdef TF2_DLL
|
|
m_animate = 1;
|
|
#else
|
|
m_animate = 0;
|
|
m_flPlaybackRate = 0;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
m_animate = 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// cycler think
|
|
//
|
|
void CCycler::Think( void )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
if (m_animate)
|
|
{
|
|
StudioFrameAdvance ( );
|
|
DispatchAnimEvents( this );
|
|
}
|
|
if (IsSequenceFinished() && !SequenceLoops())
|
|
{
|
|
// ResetSequenceInfo();
|
|
// hack to avoid reloading model every frame
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
m_flPlaybackRate = 1.0;
|
|
m_bSequenceFinished = false;
|
|
m_flLastEventCheck = 0;
|
|
m_flCycle = 0;
|
|
if (!m_animate)
|
|
m_flPlaybackRate = 0.0; // FIX: don't reset framerate
|
|
}
|
|
}
|
|
|
|
//
|
|
// CyclerUse - starts a rotation trend
|
|
//
|
|
void CCycler::Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
|
|
{
|
|
m_animate = !m_animate;
|
|
if (m_animate)
|
|
m_flPlaybackRate = 1.0;
|
|
else
|
|
m_flPlaybackRate = 0.0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Changes sequences when hurt.
|
|
//-----------------------------------------------------------------------------
|
|
int CCycler::OnTakeDamage( const CTakeDamageInfo &info )
|
|
{
|
|
if (m_animate)
|
|
{
|
|
int nSequence = GetSequence() + 1;
|
|
if ( !IsValidSequence(nSequence) )
|
|
{
|
|
nSequence = 0;
|
|
}
|
|
|
|
ResetSequence( nSequence );
|
|
m_flCycle = 0;
|
|
}
|
|
else
|
|
{
|
|
m_flPlaybackRate = 1.0;
|
|
StudioFrameAdvance ();
|
|
m_flPlaybackRate = 0;
|
|
Msg( "sequence: %d, frame %.0f\n", GetSequence(), m_flCycle );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Input that sets the sequence of the cycler
|
|
//-----------------------------------------------------------------------------
|
|
void CCycler::InputSetSequence( inputdata_t &inputdata )
|
|
{
|
|
if (m_animate)
|
|
{
|
|
// Legacy support: Try it as a number, and support '0'
|
|
const char *sChar = inputdata.value.String();
|
|
int iSeqNum = atoi( sChar );
|
|
if ( !iSeqNum && sChar[0] != '0' )
|
|
{
|
|
// Treat it as a sequence name
|
|
ResetSequence( LookupSequence( sChar ) );
|
|
}
|
|
else
|
|
{
|
|
ResetSequence( iSeqNum );
|
|
}
|
|
|
|
if (GetPlaybackRate() == 0.0)
|
|
{
|
|
ResetSequence( 0 );
|
|
}
|
|
|
|
m_flCycle = 0;
|
|
}
|
|
}
|
|
|
|
// FIXME: this doesn't work anymore, and hasn't for a while now.
|
|
|
|
class CWeaponCycler : public CBaseCombatWeapon
|
|
{
|
|
DECLARE_DATADESC();
|
|
public:
|
|
DECLARE_CLASS( CWeaponCycler, CBaseCombatWeapon );
|
|
|
|
DECLARE_SERVERCLASS();
|
|
|
|
void Spawn( void );
|
|
|
|
void PrimaryAttack( void );
|
|
void SecondaryAttack( void );
|
|
bool Deploy( void );
|
|
bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
|
|
string_t m_iszModel;
|
|
int m_iModel;
|
|
};
|
|
|
|
IMPLEMENT_SERVERCLASS_ST(CWeaponCycler, DT_WeaponCycler)
|
|
END_SEND_TABLE()
|
|
|
|
LINK_ENTITY_TO_CLASS( cycler_weapon, CWeaponCycler );
|
|
|
|
//---------------------------------------------------------
|
|
// Save/Restore
|
|
//---------------------------------------------------------
|
|
BEGIN_DATADESC( CWeaponCycler )
|
|
|
|
DEFINE_FIELD( m_iszModel, FIELD_STRING ),
|
|
DEFINE_FIELD( m_iModel, FIELD_INTEGER ),
|
|
|
|
END_DATADESC()
|
|
|
|
void CWeaponCycler::Spawn( )
|
|
{
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
|
|
PrecacheModel( STRING( GetModelName() ) );
|
|
SetModel( STRING( GetModelName() ) );
|
|
m_iszModel = GetModelName();
|
|
m_iModel = GetModelIndex();
|
|
|
|
UTIL_SetSize(this, Vector(-16, -16, 0), Vector(16, 16, 16));
|
|
SetTouch( &CWeaponCycler::DefaultTouch );
|
|
}
|
|
|
|
|
|
|
|
bool CWeaponCycler::Deploy( )
|
|
{
|
|
CBaseCombatCharacter *pOwner = GetOwner();
|
|
|
|
if (pOwner)
|
|
{
|
|
pOwner->m_flNextAttack = gpGlobals->curtime + 1.0;
|
|
SendWeaponAnim( 0 );
|
|
m_iClip1 = 0;
|
|
m_iClip2 = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool CWeaponCycler::Holster( CBaseCombatWeapon *pSwitchingTo )
|
|
{
|
|
CBaseCombatCharacter *pOwner = GetOwner();
|
|
if (pOwner)
|
|
{
|
|
pOwner->m_flNextAttack = gpGlobals->curtime + 0.5;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void CWeaponCycler::PrimaryAttack()
|
|
{
|
|
SendWeaponAnim( GetSequence() );
|
|
m_flNextPrimaryAttack = gpGlobals->curtime + 0.3;
|
|
}
|
|
|
|
|
|
void CWeaponCycler::SecondaryAttack( void )
|
|
{
|
|
float flFrameRate;
|
|
|
|
int nSequence = (GetSequence() + 1) % 8;
|
|
|
|
// BUG: Why do we set this here and then set to zero right after?
|
|
SetModelIndex( m_iModel );
|
|
flFrameRate = 0.0;
|
|
|
|
SetModelIndex( 0 );
|
|
|
|
if (flFrameRate == 0.0)
|
|
{
|
|
nSequence = 0;
|
|
}
|
|
|
|
SetSequence( nSequence );
|
|
SendWeaponAnim( nSequence );
|
|
|
|
m_flNextSecondaryAttack = gpGlobals->curtime + 0.3;
|
|
}
|
|
|
|
|
|
|
|
// Flaming Wreakage
|
|
class CWreckage : public CAI_BaseNPC
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CWreckage, CAI_BaseNPC );
|
|
|
|
DECLARE_DATADESC();
|
|
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
void Think( void );
|
|
|
|
float m_flStartTime;
|
|
float m_flDieTime;
|
|
};
|
|
|
|
BEGIN_DATADESC( CWreckage )
|
|
|
|
DEFINE_FIELD( m_flStartTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flDieTime, FIELD_TIME ),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
LINK_ENTITY_TO_CLASS( cycler_wreckage, CWreckage );
|
|
|
|
void CWreckage::Spawn( void )
|
|
{
|
|
SetSolid( SOLID_NONE );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
m_takedamage = 0;
|
|
|
|
SetCycle( 0 );
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
if (GetModelName() != NULL_STRING)
|
|
{
|
|
PrecacheModel( STRING( GetModelName() ) );
|
|
SetModel( STRING( GetModelName() ) );
|
|
}
|
|
|
|
m_flStartTime = gpGlobals->curtime;
|
|
}
|
|
|
|
void CWreckage::Precache( )
|
|
{
|
|
if ( GetModelName() != NULL_STRING )
|
|
PrecacheModel( STRING( GetModelName() ) );
|
|
}
|
|
|
|
void CWreckage::Think( void )
|
|
{
|
|
StudioFrameAdvance( );
|
|
SetNextThink( gpGlobals->curtime + 0.2 );
|
|
|
|
if (m_flDieTime)
|
|
{
|
|
if (m_flDieTime < gpGlobals->curtime)
|
|
{
|
|
UTIL_Remove( this );
|
|
return;
|
|
}
|
|
else if (random->RandomFloat( 0, m_flDieTime - m_flStartTime ) > m_flDieTime - gpGlobals->curtime)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
Vector vecSrc;
|
|
CollisionProp()->RandomPointInBounds( vec3_origin, Vector(1, 1, 1), &vecSrc );
|
|
CPVSFilter filter( vecSrc );
|
|
te->Smoke( filter, 0.0,
|
|
&vecSrc, g_sModelIndexSmoke,
|
|
random->RandomFloat(0,4.9) + 5.0,
|
|
random->RandomInt(0, 3) + 8 );
|
|
}
|
|
|
|
// BlendingCycler
|
|
// Used to demonstrate animation blending
|
|
class CBlendingCycler : public CCycler
|
|
{
|
|
DECLARE_DATADESC();
|
|
public:
|
|
DECLARE_CLASS( CBlendingCycler, CCycler );
|
|
|
|
void Spawn( void );
|
|
bool KeyValue( const char *szKeyName, const char *szValue );
|
|
void Think( void );
|
|
virtual int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_DONT_SAVE | FCAP_IMPULSE_USE); }
|
|
|
|
int m_iLowerBound;
|
|
int m_iUpperBound;
|
|
int m_iCurrent;
|
|
int m_iBlendspeed;
|
|
string_t m_iszSequence;
|
|
};
|
|
LINK_ENTITY_TO_CLASS( cycler_blender, CBlendingCycler );
|
|
|
|
//---------------------------------------------------------
|
|
// Save/Restore
|
|
//---------------------------------------------------------
|
|
BEGIN_DATADESC( CBlendingCycler )
|
|
|
|
DEFINE_FIELD( m_iLowerBound, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iUpperBound, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iCurrent, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iBlendspeed, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iszSequence, FIELD_STRING ),
|
|
|
|
END_DATADESC()
|
|
|
|
void CBlendingCycler::Spawn( void )
|
|
{
|
|
// Remove if it's not blending
|
|
if (m_iLowerBound == 0 && m_iUpperBound == 0)
|
|
{
|
|
UTIL_Remove( this );
|
|
return;
|
|
}
|
|
|
|
GenericCyclerSpawn( (char *)STRING( GetModelName() ), Vector(-16,-16,-16), Vector(16,16,16));
|
|
if (!m_iBlendspeed)
|
|
m_iBlendspeed = 5;
|
|
|
|
// Initialise Sequence
|
|
if (m_iszSequence != NULL_STRING)
|
|
{
|
|
SetSequence( LookupSequence( STRING(m_iszSequence) ) );
|
|
}
|
|
|
|
m_iCurrent = m_iLowerBound;
|
|
}
|
|
|
|
bool CBlendingCycler::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if (FStrEq(szKeyName, "lowboundary"))
|
|
{
|
|
m_iLowerBound = atoi(szValue);
|
|
}
|
|
else if (FStrEq(szKeyName, "highboundary"))
|
|
{
|
|
m_iUpperBound = atoi(szValue);
|
|
}
|
|
else if (FStrEq(szKeyName, "blendspeed"))
|
|
{
|
|
m_iBlendspeed = atoi(szValue);
|
|
}
|
|
else if (FStrEq(szKeyName, "blendsequence"))
|
|
{
|
|
m_iszSequence = AllocPooledString(szValue);
|
|
}
|
|
else
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
|
|
return true;
|
|
}
|
|
|
|
// Blending Cycler think
|
|
void CBlendingCycler::Think( void )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
// Move
|
|
m_iCurrent += m_iBlendspeed;
|
|
if ( (m_iCurrent > m_iUpperBound) || (m_iCurrent < m_iLowerBound) )
|
|
m_iBlendspeed = m_iBlendspeed * -1;
|
|
|
|
// Set blend
|
|
SetPoseParameter( 0, m_iCurrent );
|
|
|
|
Msg( "Current Blend: %d\n", m_iCurrent );
|
|
|
|
if (IsSequenceFinished() && !SequenceLoops())
|
|
{
|
|
// ResetSequenceInfo();
|
|
// hack to avoid reloading model every frame
|
|
m_flAnimTime = gpGlobals->curtime;
|
|
m_flPlaybackRate = 1.0;
|
|
m_bSequenceFinished = false;
|
|
m_flLastEventCheck = 0;
|
|
m_flCycle = 0;
|
|
if (!m_animate)
|
|
{
|
|
m_flPlaybackRate = 0.0; // FIX: don't reset framerate
|
|
}
|
|
}
|
|
}
|
|
|
|
|