//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// //========================================================= // Generic NPC - purely for scripted sequence work. //========================================================= #include "cbase.h" #include "shareddefs.h" #include "npcevent.h" #include "ai_basenpc.h" #include "ai_hull.h" #include "ai_baseactor.h" #include "tier1/strtools.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar flex_looktime( "flex_looktime", "5" ); //--------------------------------------------------------- // Sounds //--------------------------------------------------------- //========================================================= // NPC's Anim Events Go Here //========================================================= class CGenericActor : public CAI_BaseActor { public: DECLARE_CLASS( CGenericActor, CAI_BaseActor ); void Spawn( void ); void Precache( void ); float MaxYawSpeed( void ); Class_T Classify ( void ); void HandleAnimEvent( animevent_t *pEvent ); int GetSoundInterests ( void ); virtual int UpdateTransmitState( void ); void TempGunEffect( void ); string_t m_strHullName; DECLARE_DATADESC(); }; LINK_ENTITY_TO_CLASS( generic_actor, CGenericActor ); BEGIN_DATADESC( CGenericActor ) DEFINE_KEYFIELD(m_strHullName, FIELD_STRING, "hull_name" ), END_DATADESC() //========================================================= // Classify - indicates this NPC's place in the // relationship table. //========================================================= Class_T CGenericActor::Classify ( void ) { return CLASS_NONE; } //========================================================= // MaxYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= float CGenericActor::MaxYawSpeed ( void ) { return 90; } //========================================================= // HandleAnimEvent - catches the NPC-specific messages // that occur when tagged animation frames are played. //========================================================= void CGenericActor::HandleAnimEvent( animevent_t *pEvent ) { BaseClass::HandleAnimEvent( pEvent ); } //========================================================= // GetSoundInterests - generic NPC can't hear. //========================================================= int CGenericActor::GetSoundInterests ( void ) { return NULL; } //========================================================= // Spawn //========================================================= void CGenericActor::Spawn() { Precache(); SetModel( STRING( GetModelName() ) ); /* if ( FStrEq( STRING( GetModelName() ), "models/player.mdl" ) ) UTIL_SetSize(this, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); else UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); */ if ( FStrEq( STRING( GetModelName() ), "models/player.mdl" ) || FStrEq( STRING( GetModelName() ), "models/holo.mdl" ) || FStrEq( STRING( GetModelName() ), "models/blackout.mdl" ) ) { UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); } else { UTIL_SetSize(this, NAI_Hull::Mins(HULL_HUMAN), NAI_Hull::Maxs(HULL_HUMAN)); } if ( !FStrEq( STRING( GetModelName() ), "models/blackout.mdl" ) ) { SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); } else { SetSolid( SOLID_NONE ); } SetMoveType( MOVETYPE_STEP ); SetBloodColor( BLOOD_COLOR_RED ); m_iHealth = 8; m_flFieldOfView = 0.5;// indicates the width of this NPC's forward view cone ( as a dotproduct result ) m_NPCState = NPC_STATE_NONE; CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS ); // remove head turn if no eyes or forward attachment if (LookupAttachment( "eyes" ) > 0 && LookupAttachment( "forward" ) > 0) { CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE ); } if (m_strHullName != NULL_STRING) { SetHullType( NAI_Hull::LookupId( STRING( m_strHullName ) ) ); } else { SetHullType( HULL_HUMAN ); } SetHullSizeNormal( ); NPCInit(); } //========================================================= // Precache - precaches all resources this NPC needs //========================================================= void CGenericActor::Precache() { PrecacheModel( STRING( GetModelName() ) ); } //========================================================= // Send to all clients since we need to drive the lighted mouth with this //========================================================= int CGenericActor::UpdateTransmitState() { // ALWAYS transmit to all clients. return SetTransmitState( FL_EDICT_ALWAYS ); } //========================================================= // AI Schedules Specific to this NPC //========================================================= // ----------------------------------------------------------------------- // FIXME: delete this code class CFlextalkActor : public CGenericActor { private: DECLARE_CLASS( CFlextalkActor, CGenericActor ); public: DECLARE_DATADESC(); CFlextalkActor() { m_iszSentence = NULL_STRING; m_sentence = 0; } //void GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax); //virtual int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | FCAP_IMPULSE_USE); } //int OnTakeDamage( CBaseEntity *pInflictor, CBaseEntity *pAttacker, float flDamage, int bitsDamageType ); //void Spawn( void ); //void Precache( void ); //void Think( void ); virtual void ProcessSceneEvents( void ); // Don't treat as a live target //virtual bool IsAlive( void ) { return FALSE; } float m_flextime; LocalFlexController_t m_flexnum; float m_flextarget[64]; float m_blinktime; float m_looktime; Vector m_lookTarget; float m_speaktime; int m_istalking; int m_phoneme; string_t m_iszSentence; int m_sentence; void SetFlexTarget( LocalFlexController_t flexnum, float value ); LocalFlexController_t LookupFlex( const char *szTarget ); }; BEGIN_DATADESC( CFlextalkActor ) DEFINE_FIELD( m_flextime, FIELD_TIME ), DEFINE_FIELD( m_flexnum, FIELD_INTEGER ), DEFINE_ARRAY( m_flextarget, FIELD_FLOAT, 64 ), DEFINE_FIELD( m_blinktime, FIELD_TIME ), DEFINE_FIELD( m_looktime, FIELD_TIME ), DEFINE_FIELD( m_lookTarget, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_speaktime, FIELD_TIME ), DEFINE_FIELD( m_istalking, FIELD_INTEGER ), DEFINE_FIELD( m_phoneme, FIELD_INTEGER ), DEFINE_KEYFIELD( m_iszSentence, FIELD_STRING, "Sentence" ), DEFINE_FIELD( m_sentence, FIELD_INTEGER ), END_DATADESC() LINK_ENTITY_TO_CLASS( cycler_actor, CFlextalkActor ); extern ConVar flex_expression; extern ConVar flex_talk; // Cycler member functions extern const char *predef_flexcontroller_names[]; extern float predef_flexcontroller_values[7][30]; void CFlextalkActor::SetFlexTarget( LocalFlexController_t flexnum, float value ) { m_flextarget[flexnum] = value; const char *pszType = GetFlexControllerType( flexnum ); for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) { if (i != flexnum) { const char *pszOtherType = GetFlexControllerType( i ); if (stricmp( pszType, pszOtherType ) == 0) { m_flextarget[i] = 0; } } } float value2 = value; if (1 || random->RandomFloat( 0.0, 1.0 ) < 0.2) { value2 = random->RandomFloat( value - 0.2, value + 0.2 ); value2 = clamp( value2, 0.0, 1.0 ); } // HACK, for now, consider then linked is named "right_" or "left_" if ( StringHasPrefixCaseSensitive( GetFlexControllerName( flexnum ), "right_" ) ) { m_flextarget[flexnum+1] = value2; } else if ( StringHasPrefixCaseSensitive( GetFlexControllerName( flexnum ), "left_" ) ) { m_flextarget[flexnum-1] = value2; } } LocalFlexController_t CFlextalkActor::LookupFlex( const char *szTarget ) { for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) { const char *pszFlex = GetFlexControllerName( i ); if (stricmp( szTarget, pszFlex ) == 0) { return i; } } return LocalFlexController_t(-1); } void CFlextalkActor::ProcessSceneEvents( void ) { if ( HasSceneEvents() ) { BaseClass::ProcessSceneEvents( ); return; } // only do this if they have more than eyelid movement if (GetNumFlexControllers() > 2) { const char *pszExpression = flex_expression.GetString(); if (pszExpression && pszExpression[0] == '+' && pszExpression[1] != '\0') { int i; int j = atoi( &pszExpression[1] ); for (i = 0; i < GetNumFlexControllers(); i++) { m_flextarget[m_flexnum] = 0; } for (i = 0; i < 35 && predef_flexcontroller_names[i]; i++) { m_flexnum = LookupFlex( predef_flexcontroller_names[i] ); m_flextarget[m_flexnum] = predef_flexcontroller_values[j][i]; // Msg( "%s %.3f\n", predef_flexcontroller_names[i], predef_flexcontroller_values[j][i] ); } } else if (pszExpression && pszExpression[0] != '\0' && strcmp(pszExpression, "+") != 0) { char szExpression[128]; char szTemp[32]; Q_strncpy( szExpression, pszExpression ,sizeof(szExpression)); char *pszExpression = szExpression; while (*pszExpression != '\0') { if (*pszExpression == '+') *pszExpression = ' '; pszExpression++; } pszExpression = szExpression; while (*pszExpression) { if (*pszExpression != ' ') { if (*pszExpression == '-') { for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) { m_flextarget[i] = 0; } } else if (*pszExpression == '?') { for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) { Msg( "\"%s\" ", GetFlexControllerName( i ) ); } Msg( "\n" ); flex_expression.SetValue( "" ); } else { if (sscanf( pszExpression, "%31s", szTemp ) == 1) { m_flexnum = LookupFlex( szTemp ); if (m_flexnum != -1 && m_flextarget[m_flexnum] != 1) { m_flextarget[m_flexnum] = 1.0; // SetFlexTarget( m_flexnum ); } pszExpression += strlen( szTemp ) - 1; } } } pszExpression++; } } else if (m_flextime < gpGlobals->curtime) { m_flextime = gpGlobals->curtime + random->RandomFloat( 0.3, 0.5 ) * (30.0 / GetNumFlexControllers()); m_flexnum = (LocalFlexController_t)random->RandomInt( 0, GetNumFlexControllers() - 1 ); if (m_flextarget[m_flexnum] == 1) { m_flextarget[m_flexnum] = 0; } else if (stricmp( GetFlexControllerType( m_flexnum ), "phoneme" ) != 0) { if (strstr( GetFlexControllerName( m_flexnum ), "upper_raiser" ) == NULL) { Msg( "%s:%s\n", GetFlexControllerType( m_flexnum ), GetFlexControllerName( m_flexnum ) ); SetFlexTarget( m_flexnum, random->RandomFloat( 0.5, 1.0 ) ); } } } // slide it up. for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++) { float weight = GetFlexWeight( i ); if (weight != m_flextarget[i]) { weight = weight + (m_flextarget[i] - weight) / random->RandomFloat( 2.0, 4.0 ); } weight = clamp( weight, 0.0f, 1.0f ); SetFlexWeight( i, weight ); } if (flex_talk.GetInt() == -1) { m_istalking = 1; char pszSentence[256]; Q_snprintf( pszSentence,sizeof(pszSentence), "%s%d", STRING(m_iszSentence), m_sentence++ ); int sentenceIndex = engine->SentenceIndexFromName( pszSentence ); if (sentenceIndex >= 0) { Msg( "%d : %s\n", sentenceIndex, pszSentence ); CPASAttenuationFilter filter( this ); CBaseEntity::EmitSentenceByIndex( filter, entindex(), CHAN_VOICE, sentenceIndex, 1, SNDLVL_TALKING, 0, PITCH_NORM ); } else { m_sentence = 0; } flex_talk.SetValue( "0" ); } else if (flex_talk.GetInt() == -2) { m_flNextEyeLookTime = gpGlobals->curtime + 1000.0; } else if (flex_talk.GetInt() == -3) { m_flNextEyeLookTime = gpGlobals->curtime; flex_talk.SetValue( "0" ); } else if (flex_talk.GetInt() == -4) { AddLookTarget( UTIL_PlayerByIndex( 1 ), 0.5, flex_looktime.GetFloat() ); flex_talk.SetValue( "0" ); } else if (flex_talk.GetInt() == -5) { PickLookTarget( true ); flex_talk.SetValue( "0" ); } } }