📄 hl1_npc_scientist.cpp
字号:
//=========== (C) Copyright 2000 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
// events
//
// $Workfile: $
// $Date: $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "AI_Default.h"
#include "AI_Task.h"
#include "AI_Schedule.h"
#include "AI_Node.h"
#include "AI_Hull.h"
#include "AI_Hint.h"
#include "AI_Route.h"
#include "hl1_npc_scientist.h"
#include "soundent.h"
#include "game.h"
#include "NPCEvent.h"
#include "EntityList.h"
#include "activitylist.h"
#include "animation.h"
#include "engine/IEngineSound.h"
#include "ai_navigator.h"
#include "AI_Behavior_Follow.h"
#include "AI_Criteria.h"
#include "doors.h"
#define SC_PLFEAR "SC_PLFEAR"
#define SC_FEAR "SC_FEAR"
#define SC_HEAL "SC_HEAL"
#define SC_SCREAM "SC_SCREAM"
#define SC_POK "SC_POK"
ConVar sk_scientist_health( "sk_scientist_health","20");
ConVar sk_scientist_heal( "sk_scientist_heal","25");
#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model
enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 };
int ACT_EXCITED;
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define SCIENTIST_AE_HEAL ( 1 )
#define SCIENTIST_AE_NEEDLEON ( 2 )
#define SCIENTIST_AE_NEEDLEOFF ( 3 )
//=======================================================
// Scientist
//=======================================================
LINK_ENTITY_TO_CLASS( monster_scientist, CNPC_Scientist );
//IMPLEMENT_SERVERCLASS_ST( CNPC_Scientist, DT_NPC_Scientist )
//END_SEND_TABLE()
//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CNPC_Scientist )
DEFINE_FIELD( CNPC_Scientist, m_flFearTime, FIELD_TIME ),
DEFINE_FIELD( CNPC_Scientist, m_flHealTime, FIELD_TIME ),
DEFINE_FIELD( CNPC_Scientist, m_flPainTime, FIELD_TIME ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//
//
//-----------------------------------------------------------------------------
void CNPC_Scientist::Precache( void )
{
engine->PrecacheModel( "models/scientist.mdl" );
enginesound->PrecacheSound("scientist/sci_pain1.wav");
enginesound->PrecacheSound("scientist/sci_pain2.wav");
enginesound->PrecacheSound("scientist/sci_pain3.wav");
enginesound->PrecacheSound("scientist/sci_pain4.wav");
enginesound->PrecacheSound("scientist/sci_pain5.wav");
enginesound->PrecacheSound("scientist/c1a4_sci_rocket.wav");
TalkInit();
BaseClass::Precache();
}
void CNPC_Scientist::ModifyOrAppendCriteria( AI_CriteriaSet& set )
{
BaseClass::ModifyOrAppendCriteria( set );
bool predisaster = FBitSet( m_spawnflags, SF_NPC_PREDISASTER ) ? true : false;
set.AppendCriteria( "disaster", predisaster ? "[disaster::pre]" : "[disaster::post]" );
}
// Init talk data
void CNPC_Scientist::TalkInit()
{
BaseClass::TalkInit();
// scientist will try to talk to friends in this order:
m_szFriends[0] = "monster_scientist";
m_szFriends[1] = "monster_sitting_scientist";
m_szFriends[2] = "monster_barney";
// get voice for head
switch (m_nBody % 3)
{
default:
case HEAD_GLASSES: GetExpresser()->SetVoicePitch( 105 ); break; //glasses
case HEAD_EINSTEIN: GetExpresser()->SetVoicePitch( 100 ); break; //einstein
case HEAD_LUTHER: GetExpresser()->SetVoicePitch( 95 ); break; //luther
case HEAD_SLICK: GetExpresser()->SetVoicePitch( 100 ); break;//slick
}
}
//-----------------------------------------------------------------------------
// Purpose:
//
//
//-----------------------------------------------------------------------------
void CNPC_Scientist::Spawn( void )
{
//Select the body first if it's going to be random cause we set his voice pitch in Precache.
if ( m_nBody == -1 )
m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1 );// pick a head, any head
SetRenderColor( 255, 255, 255, 255 );
Precache();
SetModel( "models/scientist.mdl" );
SetHullType(HULL_HUMAN);
SetHullSizeNormal();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
m_bloodColor = BLOOD_COLOR_RED;
m_fEffects = 0;
m_iHealth = sk_scientist_health.GetFloat();
m_flFieldOfView = VIEW_FIELD_WIDE;
m_NPCState = NPC_STATE_NONE;
CapabilitiesClear();
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE );
CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE );
// White hands
m_nSkin = 0;
// Luther is black, make his hands black
if ( m_nBody == HEAD_LUTHER )
m_nSkin = 1;
NPCInit();
SetUse( FollowerUse );
}
//-----------------------------------------------------------------------------
// Purpose:
//
//
// Output :
//-----------------------------------------------------------------------------
Class_T CNPC_Scientist::Classify( void )
{
return CLASS_HUMAN_PASSIVE;
}
int CNPC_Scientist :: GetSoundInterests ( void )
{
return SOUND_WORLD |
SOUND_COMBAT |
SOUND_DANGER |
SOUND_PLAYER;
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CNPC_Scientist::HandleAnimEvent( animevent_t *pEvent )
{
switch( pEvent->event )
{
case SCIENTIST_AE_HEAL: // Heal my target (if within range)
Heal();
break;
case SCIENTIST_AE_NEEDLEON:
{
int oldBody = m_nBody;
m_nBody = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1;
}
break;
case SCIENTIST_AE_NEEDLEOFF:
{
int oldBody = m_nBody;
m_nBody = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0;
}
break;
default:
BaseClass::HandleAnimEvent( pEvent );
break;
}
}
void CNPC_Scientist::DeclineFollowing( void )
{
GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 10 );
//SetSpeechTarget( GetTarget() );
Speak( SC_POK );
}
void CNPC_Scientist :: Scream( void )
{
if ( IsOkToSpeak() )
{
GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 10 );
SetSpeechTarget( GetEnemy() );
Speak( SC_SCREAM );
}
}
Activity CNPC_Scientist::GetStoppedActivity( void )
{
if ( GetEnemy() != NULL )
return (Activity)ACT_EXCITED;
return BaseClass::GetStoppedActivity();
}
float CNPC_Scientist::MaxYawSpeed( void )
{
switch( GetActivity() )
{
case ACT_TURN_LEFT:
case ACT_TURN_RIGHT:
return 160;
break;
case ACT_RUN:
return 160;
break;
default:
return 60;
break;
}
}
void CNPC_Scientist :: StartTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_SAY_HEAL:
GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 2 );
SetSpeechTarget( GetTarget() );
Speak( SC_HEAL );
TaskComplete();
break;
case TASK_SCREAM:
Scream();
TaskComplete();
break;
case TASK_RANDOM_SCREAM:
if ( random->RandomFloat( 0, 1 ) < pTask->flTaskData )
Scream();
TaskComplete();
break;
case TASK_SAY_FEAR:
if ( IsOkToSpeak() )
{
GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 2 );
SetSpeechTarget( GetEnemy() );
if ( GetEnemy() && GetEnemy()->IsPlayer() )
Speak( SC_PLFEAR );
else
Speak( SC_FEAR );
}
TaskComplete();
break;
case TASK_HEAL:
SetIdealActivity( ACT_MELEE_ATTACK1 );
break;
case TASK_RUN_PATH_SCARED:
GetNavigator()->SetMovementActivity( ACT_RUN_SCARED );
break;
case TASK_MOVE_TO_TARGET_RANGE_SCARED:
{
if ( GetTarget() == NULL)
{
TaskFail(FAIL_NO_TARGET);
}
else if ( (GetTarget()->GetAbsOrigin() - GetAbsOrigin()).Length() < 1 )
{
TaskComplete();
}
}
break;
default:
BaseClass::StartTask( pTask );
break;
}
}
void CNPC_Scientist :: RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_RUN_PATH_SCARED:
if ( !IsMoving() )
TaskComplete();
if ( random->RandomInt(0,31) < 8 )
Scream();
break;
case TASK_MOVE_TO_TARGET_RANGE_SCARED:
{
float distance;
if ( GetTarget() == NULL )
{
TaskFail(FAIL_NO_TARGET);
}
else
{
distance = ( GetNavigator()->GetPath()->ActualGoalPosition() - GetAbsOrigin() ).Length2D();
// Re-evaluate when you think your finished, or the target has moved too far
if ( (distance < pTask->flTaskData) || (GetNavigator()->GetPath()->ActualGoalPosition() - GetTarget()->GetAbsOrigin()).Length() > pTask->flTaskData * 0.5 )
{
GetNavigator()->GetPath()->ResetGoalPosition(GetTarget()->GetAbsOrigin());
distance = ( GetNavigator()->GetPath()->ActualGoalPosition() - GetAbsOrigin() ).Length2D();
// GetNavigator()->GetPath()->Find();
GetNavigator()->SetGoal( GOALTYPE_TARGETENT );
}
// Set the appropriate activity based on an overlapping range
// overlap the range to prevent oscillation
// BUGBUG: this is checking linear distance (ie. through walls) and not path distance or even visibility
if ( distance < pTask->flTaskData )
{
TaskComplete();
GetNavigator()->GetPath()->Clear(); // Stop moving
}
else
{
if ( distance < 190 && GetNavigator()->GetMovementActivity() != ACT_WALK_SCARED )
GetNavigator()->SetMovementActivity( ACT_WALK_SCARED );
else if ( distance >= 270 && GetNavigator()->GetMovementActivity() != ACT_RUN_SCARED )
GetNavigator()->SetMovementActivity( ACT_RUN_SCARED );
}
}
}
break;
case TASK_HEAL:
if ( IsSequenceFinished() )
{
TaskComplete();
}
else
{
if ( TargetDistance() > 90 )
TaskComplete();
if ( GetTarget() )
GetMotor()->SetIdealYaw( UTIL_VecToYaw( GetTarget()->GetAbsOrigin() - GetAbsOrigin() ) );
//GetMotor()->SetYawSpeed( m_YawSpeed );
}
break;
default:
BaseClass::RunTask( pTask );
break;
}
}
int CNPC_Scientist :: OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{
if ( inputInfo.GetInflictor() && inputInfo.GetInflictor()->GetFlags() & FL_CLIENT )
{
Remember( bits_MEMORY_PROVOKED );
StopFollowing();
}
// make sure friends talk about it if player hurts scientist...
return BaseClass::OnTakeDamage_Alive( inputInfo );
}
void CNPC_Scientist::Event_Killed( const CTakeDamageInfo &info )
{
SetUse( NULL );
BaseClass::Event_Killed( info );
}
bool CNPC_Scientist::CanHeal( void )
{
CBaseEntity *pTarget = GetFollowTarget();
if ( pTarget == NULL )
return false;
if ( pTarget->IsPlayer() == false )
return false;
if ( (m_flHealTime > gpGlobals->curtime) || (pTarget->m_iHealth > (pTarget->m_iMaxHealth * 0.5)) )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Scientist::OnUpcomingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor, float distClear, AIMoveResult_t *pResult )
{
// If we can't get through the door, try and open it
if ( BaseClass::OnUpcomingDoor( 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 );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -