diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl1/hl1_npc_talker.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/hl1/hl1_npc_talker.cpp')
| -rw-r--r-- | game/server/hl1/hl1_npc_talker.cpp | 728 |
1 files changed, 728 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_talker.cpp b/game/server/hl1/hl1_npc_talker.cpp new file mode 100644 index 0000000..2797cec --- /dev/null +++ b/game/server/hl1/hl1_npc_talker.cpp @@ -0,0 +1,728 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "cbase.h" +#include "hl1_npc_talker.h" +#include "scripted.h" +#include "soundent.h" +#include "animation.h" +#include "entitylist.h" +#include "ai_navigator.h" +#include "ai_motor.h" +#include "player.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "npcevent.h" +#include "ai_interactions.h" +#include "doors.h" + +#include "effect_dispatch_data.h" +#include "te_effect_dispatch.h" +#include "hl1_ai_basenpc.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" + +ConVar hl1_debug_sentence_volume( "hl1_debug_sentence_volume", "0" ); +ConVar hl1_fixup_sentence_sndlevel( "hl1_fixup_sentence_sndlevel", "1" ); + +//#define TALKER_LOOK 0 + +BEGIN_DATADESC( CHL1NPCTalker ) + + DEFINE_ENTITYFUNC( Touch ), + DEFINE_FIELD( m_bInBarnacleMouth, FIELD_BOOLEAN ), + DEFINE_USEFUNC( FollowerUse ), + +END_DATADESC() + +void CHL1NPCTalker::RunTask( const Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS: + { + float distance; + + distance = (m_vecLastPosition - GetLocalOrigin()).Length2D(); + + // Walk path until far enough away + if ( distance > pTask->flTaskData || + GetNavigator()->GetGoalType() == GOALTYPE_NONE ) + { + TaskComplete(); + GetNavigator()->ClearGoal(); // Stop moving + } + break; + } + + + + case TASK_TALKER_CLIENT_STARE: + case TASK_TALKER_LOOK_AT_CLIENT: + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + + // track head to the client for a while. + if ( m_NPCState == NPC_STATE_IDLE && + !IsMoving() && + !GetExpresser()->IsSpeaking() ) + { + + if ( pPlayer ) + { + IdleHeadTurn( pPlayer ); + } + } + else + { + // started moving or talking + TaskFail( "moved away" ); + return; + } + + if ( pTask->iTask == TASK_TALKER_CLIENT_STARE ) + { + // fail out if the player looks away or moves away. + if ( ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).Length2D() > TALKER_STARE_DIST ) + { + // player moved away. + TaskFail( NO_TASK_FAILURE ); + } + + Vector vForward; + AngleVectors( GetAbsAngles(), &vForward ); + if ( UTIL_DotPoints( pPlayer->GetAbsOrigin(), GetAbsOrigin(), vForward ) < m_flFieldOfView ) + { + // player looked away + TaskFail( "looked away" ); + } + } + + if ( gpGlobals->curtime > m_flWaitFinished ) + { + TaskComplete( NO_TASK_FAILURE ); + } + + break; + } + + case TASK_WAIT_FOR_MOVEMENT: + { + if ( GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL) + { + // ALERT(at_console, "walking, talking\n"); + IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime ); + } + else if ( GetEnemy() ) + { + IdleHeadTurn( GetEnemy() ); + } + + BaseClass::RunTask( pTask ); + + break; + } + + case TASK_FACE_PLAYER: + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + + if ( pPlayer ) + { + //GetMotor()->SetIdealYaw( pPlayer->GetAbsOrigin() ); + IdleHeadTurn( pPlayer ); + if ( gpGlobals->curtime > m_flWaitFinished && GetMotor()->DeltaIdealYaw() < 10 ) + { + TaskComplete(); + } + } + else + { + TaskFail( FAIL_NO_PLAYER ); + } + + break; + } + + case TASK_TALKER_EYECONTACT: + { + if (!IsMoving() && GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL) + { + // ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time ); + IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime ); + } + + BaseClass::RunTask( pTask ); + + break; + + } + + + default: + { + if ( GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL) + { + IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime ); + } + else if ( GetEnemy() && m_NPCState == NPC_STATE_COMBAT ) + { + IdleHeadTurn( GetEnemy() ); + } + else if ( GetFollowTarget() ) + { + IdleHeadTurn( GetFollowTarget() ); + } + + BaseClass::RunTask( pTask ); + break; + } + } +} + +bool CHL1NPCTalker::ShouldGib( const CTakeDamageInfo &info ) +{ + if ( info.GetDamageType() & DMG_NEVERGIB ) + return false; + + if ( ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) && m_iHealth < GIB_HEALTH_VALUE ) || ( info.GetDamageType() & DMG_ALWAYSGIB ) ) + return true; + + return false; + +} + +void CHL1NPCTalker::StartTask( const Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS: + { + GetNavigator()->SetMovementActivity( ACT_WALK ); + break; + } + case TASK_TALKER_SPEAK: + // ask question or make statement + FIdleSpeak(); + TaskComplete(); + break; + default: + BaseClass::StartTask( pTask ); + break; + } +} + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CHL1NPCTalker::FIdleSpeak ( void ) +{ + if (!IsOkToSpeak()) + return FALSE; + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + // try to talk to any standing or sitting scientists nearby + CBaseEntity *pentFriend = FindNearestFriend( false ); + CHL1NPCTalker *pentTalker = dynamic_cast<CHL1NPCTalker *>( pentFriend ); + if (pentTalker && random->RandomInt(0,1) ) + { + Speak( TLK_QUESTION ); + SetSpeechTarget( pentFriend ); + + pentTalker->SetSpeechTarget( this ); + pentTalker->SetCondition( COND_TALKER_RESPOND_TO_QUESTION ); + pentTalker->SetSchedule( SCHED_TALKER_IDLE_RESPONSE ); + pentTalker->GetExpresser()->BlockSpeechUntil( GetExpresser()->GetTimeSpeechComplete() ); + + GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + random->RandomFloat(4.8, 5.2) ); + + //DevMsg( "Asking some question!\n" ); + return TRUE; + } + else if ( random->RandomInt(0,1)) // otherwise, play an idle statement + { + //DevMsg( "Making idle statement!\n" ); + + Speak( TLK_IDLE ); + // set global min delay for next conversation + GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + random->RandomFloat(4.8, 5.2) ); + return TRUE; + } + + // never spoke + GetExpresser()->BlockSpeechUntil( 0 ); + m_flNextIdleSpeechTime = gpGlobals->curtime + 3; + return FALSE; +} + + + +bool CHL1NPCTalker::IsValidSpeechTarget( int flags, CBaseEntity *pEntity ) +{ + if ( pEntity == this ) + return false; + + CHL1NPCTalker *pentTarget = dynamic_cast<CHL1NPCTalker *>( pEntity ); + if ( pentTarget ) + { + if ( !(flags & AIST_IGNORE_RELATIONSHIP) ) + { + if ( pEntity->IsPlayer() ) + { + if ( !IsPlayerAlly( (CBasePlayer *)pEntity ) ) + return false; + } + else + { + if ( IRelationType( pEntity ) != D_LI ) + return false; + } + } + + if ( !pEntity->IsAlive() ) + // don't dead people + return false; + + // Ignore no-target entities + if ( pEntity->GetFlags() & FL_NOTARGET ) + return false; + + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if ( pNPC ) + { + // If not a NPC for some reason, or in a script. + //if ( (pNPC->m_NPCState == NPC_STATE_SCRIPT || pNPC->m_NPCState == NPC_STATE_PRONE)) + // return false; + + if ( pNPC->IsInAScript() ) + return false; + + // Don't bother people who don't want to be bothered + if ( !pNPC->CanBeUsedAsAFriend() ) + return false; + } + + if ( flags & AIST_FACING_TARGET ) + { + if ( pEntity->IsPlayer() ) + return HasCondition( COND_SEE_PLAYER ); + else if ( !FInViewCone( pEntity ) ) + return false; + } + + return FVisible( pEntity ); + } + else + return BaseClass::IsValidSpeechTarget( flags, pEntity ); +} + + +int CHL1NPCTalker::SelectSchedule ( void ) +{ + switch( m_NPCState ) + { + case NPC_STATE_PRONE: + { + if (m_bInBarnacleMouth) + { + return SCHED_HL1TALKER_BARNACLE_CHOMP; + } + else + { + return SCHED_HL1TALKER_BARNACLE_HIT; + } + } + } + + return BaseClass::SelectSchedule(); +} + +void CHL1NPCTalker::Precache() +{ + BaseClass::Precache(); + + PrecacheScriptSound( "Barney.Close" ); +} + +bool CHL1NPCTalker::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) +{ + if (interactionType == g_interactionBarnacleVictimDangle) + { + // Force choosing of a new schedule + ClearSchedule( "NPC talker being eaten by a barnacle" ); + m_bInBarnacleMouth = true; + return true; + } + else if ( interactionType == g_interactionBarnacleVictimReleased ) + { + SetState ( NPC_STATE_IDLE ); + + CPASAttenuationFilter filter( this ); + + CSoundParameters params; + + if ( GetParametersForSound( "Barney.Close", params, NULL ) ) + { + EmitSound_t ep( params ); + ep.m_nPitch = GetExpresser()->GetVoicePitch(); + + EmitSound( filter, entindex(), ep ); + } + + m_bInBarnacleMouth = false; + SetAbsVelocity( vec3_origin ); + SetMoveType( MOVETYPE_STEP ); + return true; + } + else if ( interactionType == g_interactionBarnacleVictimGrab ) + { + if ( GetFlags() & FL_ONGROUND ) + { + SetGroundEntity( NULL ); + } + + if ( GetState() == NPC_STATE_SCRIPT ) + { + if ( m_hCine ) + { + m_hCine->CancelScript(); + } + } + + SetState( NPC_STATE_PRONE ); + ClearSchedule( "NPC talker grabbed by a barnacle" ); + + CTakeDamageInfo info; + PainSound( info ); + return true; + } + return false; +} + +void CHL1NPCTalker::StartFollowing( CBaseEntity *pLeader ) +{ + if ( !HasSpawnFlags( SF_NPC_GAG ) ) + { + if ( m_iszUse != NULL_STRING ) + { + PlaySentence( STRING( m_iszUse ), 0.0f ); + } + else + { + Speak( TLK_STARTFOLLOW ); + } + + SetSpeechTarget( pLeader ); + } + + BaseClass::StartFollowing( pLeader ); +} + +int CHL1NPCTalker::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener ) +{ + if( hl1_debug_sentence_volume.GetBool() ) + { + Msg( "SENTENCE: %s Vol:%f SndLevel:%d\n", GetDebugName(), volume, soundlevel ); + } + + if( hl1_fixup_sentence_sndlevel.GetBool() ) + { + if( soundlevel < SNDLVL_TALKING ) + { + soundlevel = SNDLVL_TALKING; + } + } + + return BaseClass::PlayScriptedSentence( pszSentence, delay, volume, soundlevel, bConcurrent, pListener ); +} + +Disposition_t CHL1NPCTalker::IRelationType( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + { + if ( HasMemory( bits_MEMORY_PROVOKED ) ) + { + return D_HT; + } + } + + return BaseClass::IRelationType( pTarget ); +} + +void CHL1NPCTalker::Touch( CBaseEntity *pOther ) +{ + if ( m_NPCState == NPC_STATE_SCRIPT ) + return; + + BaseClass::Touch(pOther); +} + +void CHL1NPCTalker::StopFollowing( void ) +{ + if ( !(m_afMemory & bits_MEMORY_PROVOKED) ) + { + if ( !HasSpawnFlags( SF_NPC_GAG ) ) + { + if ( m_iszUnUse != NULL_STRING ) + { + PlaySentence( STRING( m_iszUnUse ), 0.0f ); + } + else + { + Speak( TLK_STOPFOLLOW ); + } + + SetSpeechTarget( GetFollowTarget() ); + } + } + + BaseClass::StopFollowing(); +} + +void CHL1NPCTalker::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + if ( info.GetDamage() >= 1.0 && !(info.GetDamageType() & DMG_SHOCK ) ) + { + UTIL_BloodImpact( ptr->endpos, vecDir, BloodColor(), 4 ); + } + + BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); +} + +void CHL1NPCTalker::FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Don't allow use during a scripted_sentence + if ( GetUseTime() > gpGlobals->curtime ) + return; + + if ( m_hCine && !m_hCine->CanInterrupt() ) + return; + + if ( pCaller != NULL && pCaller->IsPlayer() ) + { + // Pre-disaster followers can't be used + if ( m_spawnflags & SF_NPC_PREDISASTER ) + { + SetSpeechTarget( pCaller ); + DeclineFollowing(); + return; + } + } + + BaseClass::FollowerUse( pActivator, pCaller, useType, value ); +} + +int CHL1NPCTalker::TranslateSchedule( int scheduleType ) +{ + return BaseClass::TranslateSchedule( scheduleType ); +} + +float CHL1NPCTalker::PickLookTarget( bool bExcludePlayers, float minTime, float maxTime ) +{ + return random->RandomFloat( 5.0f, 10.0f ); +} + +void CHL1NPCTalker::IdleHeadTurn( CBaseEntity *pTarget, float flDuration, float flImportance ) +{ + // Must be able to turn our head + if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD)) + return; + + // If the target is invalid, or we're in a script, do nothing + if ( ( !pTarget ) || ( m_NPCState == NPC_STATE_SCRIPT ) ) + return; + + // Fill in a duration if we haven't specified one + if ( flDuration == 0.0f ) + { + flDuration = random->RandomFloat( 2.0, 4.0 ); + } + + // Add a look target + AddLookTarget( pTarget, 1.0, flDuration ); +} + +void CHL1NPCTalker::SetHeadDirection( const Vector &vTargetPos, float flInterval) +{ +#ifdef TALKER_LOOK + // Draw line in body, head, and eye directions + Vector vEyePos = EyePosition(); + Vector vHeadDir = HeadDirection3D(); + Vector vBodyDir = BodyDirection2D(); + + //UNDONE <<TODO>> + // currently eye dir just returns head dir, so use vTargetPos for now + //Vector vEyeDir; w + //EyeDirection3D(&vEyeDir); + NDebugOverlay::Line( vEyePos, vEyePos+(50*vHeadDir), 255, 0, 0, false, 0.1 ); + NDebugOverlay::Line( vEyePos, vEyePos+(50*vBodyDir), 0, 255, 0, false, 0.1 ); + NDebugOverlay::Line( vEyePos, vTargetPos, 0, 0, 255, false, 0.1 ); +#endif + +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHL1NPCTalker::CorpseGib( const CTakeDamageInfo &info ) +{ + CEffectData data; + + data.m_vOrigin = WorldSpaceCenter(); + data.m_vNormal = data.m_vOrigin - info.GetDamagePosition(); + VectorNormalize( data.m_vNormal ); + + data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 ); + data.m_flScale = clamp( data.m_flScale, 1, 3 ); + + data.m_nMaterial = 1; + data.m_nHitBox = -m_iHealth; + + data.m_nColor = BloodColor(); + + DispatchEffect( "HL1Gib", data ); + + CSoundEnt::InsertSound( SOUND_MEAT, GetAbsOrigin(), 256, 0.5f, this ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CHL1NPCTalker::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor, float distClear, AIMoveResult_t *pResult ) +{ + // If we can't get through the door, try and open it + if ( BaseClass::OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) ) + { + if ( IsMoveBlocked( *pResult ) && pMoveGoal->directTrace.vHitNormal != vec3_origin ) + { + // Can't do anything if the door's locked + if ( !pDoor->m_bLocked && !pDoor->HasSpawnFlags(SF_DOOR_NONPCS) ) + { + // Tell the door to open + variant_t emptyVariant; + pDoor->AcceptInput( "Open", this, this, emptyVariant, USE_TOGGLE ); + *pResult = AIMR_OK; + } + } + return true; + } + + return false; +} + +// HL1 version - never return Ragdoll as the automatic schedule at the end of a +// scripted sequence +int CHL1NPCTalker::SelectDeadSchedule() +{ + // Alread dead (by animation event maybe?) + // Is it safe to set it to SCHED_NONE? + if ( m_lifeState == LIFE_DEAD ) + return SCHED_NONE; + + CleanupOnDeath(); + return SCHED_DIE; +} + + +AI_BEGIN_CUSTOM_NPC( monster_hl1talker, CHL1NPCTalker ) + + DECLARE_TASK( TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS ) + + //========================================================= + // > SCHED_HL1TALKER_MOVE_AWAY_FOLLOW + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_HL1TALKER_FOLLOW_MOVE_AWAY, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_TARGET_FACE" + " TASK_STORE_LASTPOSITION 0" + " TASK_MOVE_AWAY_PATH 100" + " TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS 100" + " TASK_STOP_MOVING 0" + " TASK_FACE_PLAYER 0" + " TASK_SET_ACTIVITY ACT_IDLE" + "" + " Interrupts" + ) + + //========================================================= + // > SCHED_HL1TALKER_IDLE_SPEAK_WAIT + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_HL1TALKER_IDLE_SPEAK_WAIT, + + " Tasks" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Stop and talk + " TASK_FACE_PLAYER 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_HEAR_DANGER" + ) + + //========================================================= + // > SCHED_HL1TALKER_BARNACLE_HIT + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_HL1TALKER_BARNACLE_HIT, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_HIT" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_HL1TALKER_BARNACLE_PULL" + "" + " Interrupts" + ) + + //========================================================= + // > SCHED_HL1TALKER_BARNACLE_PULL + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_HL1TALKER_BARNACLE_PULL, + + " Tasks" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_PULL" + "" + " Interrupts" + ) + + //========================================================= + // > SCHED_HL1TALKER_BARNACLE_CHOMP + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_HL1TALKER_BARNACLE_CHOMP, + + " Tasks" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHOMP" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_HL1TALKER_BARNACLE_CHEW" + "" + " Interrupts" + ) + + //========================================================= + // > SCHED_HL1TALKER_BARNACLE_CHEW + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_HL1TALKER_BARNACLE_CHEW, + + " Tasks" + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHEW" + ) + +AI_END_CUSTOM_NPC() |