//========= Copyright 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 short 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.Get() ); } 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 (m_flPlaybackRate == 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 } } }