/***
*
* Copyright ( c ) 1996 - 2002 , Valve LLC . All rights reserved .
*
* This product contains software technology licensed from Id
* Software , Inc . ( " Id Technology " ) . Id Technology ( c ) 1996 Id Software , Inc .
* All Rights Reserved .
*
* This source code contains proprietary and confidential information of
* Valve LLC and its suppliers . Access to this code is restricted to
* persons who have executed a written SDK license with Valve . Any access ,
* use or distribution of this code by or to any unlicensed person is illegal .
*
* * * */
//=========================================================
// human scientist (passive lab worker)
//=========================================================
# include "extdll.h"
# include "util.h"
# include "cbase.h"
# include "monsters.h"
# include "talkmonster.h"
# include "schedule.h"
# include "defaultai.h"
# include "scripted.h"
# include "animation.h"
# include "soundent.h"
# include "scientist.h"
enum
{
SCHED_HIDE = LAST_TALKMONSTER_SCHEDULE + 1 ,
SCHED_FEAR ,
SCHED_PANIC ,
SCHED_STARTLE ,
SCHED_TARGET_CHASE_SCARED ,
SCHED_TARGET_FACE_SCARED
} ;
enum
{
TASK_SAY_HEAL = LAST_TALKMONSTER_TASK + 1 ,
TASK_HEAL ,
TASK_SAY_FEAR ,
TASK_RUN_PATH_SCARED ,
TASK_SCREAM ,
TASK_RANDOM_SCREAM ,
TASK_MOVE_TO_TARGET_RANGE_SCARED
} ;
//=========================================================
// 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 , CScientist )
# ifdef MOBILE_HACKS
// LINK_ENTITY_TO_CLASS( monster_rosenberg, CScientist )
extern " C " EXPORT void monster_rosenberg ( entvars_t * pev )
{
if ( g_iModType = = MOD_BSHIFT )
GetClassPtr ( ( CScientist * ) pev ) ;
}
# endif // MOBILE_HACKS
TYPEDESCRIPTION CScientist : : m_SaveData [ ] =
{
DEFINE_FIELD ( CScientist , m_painTime , FIELD_TIME ) ,
DEFINE_FIELD ( CScientist , m_healTime , FIELD_TIME ) ,
DEFINE_FIELD ( CScientist , m_fearTime , FIELD_TIME ) ,
} ;
IMPLEMENT_SAVERESTORE ( CScientist , CTalkMonster )
//=========================================================
// AI Schedules Specific to this monster
//=========================================================
Task_t tlFollow [ ] =
{
{ TASK_SET_FAIL_SCHEDULE , ( float ) SCHED_CANT_FOLLOW } , // If you fail, bail out of follow
{ TASK_MOVE_TO_TARGET_RANGE , 128.0f } , // Move within 128 of target ent (client)
//{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE },
} ;
Schedule_t slFollow [ ] =
{
{
tlFollow ,
ARRAYSIZE ( tlFollow ) ,
bits_COND_NEW_ENEMY |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE |
bits_COND_HEAR_SOUND ,
bits_SOUND_COMBAT |
bits_SOUND_DANGER ,
" Follow "
} ,
} ;
Task_t tlFollowScared [ ] =
{
{ TASK_SET_FAIL_SCHEDULE , ( float ) SCHED_TARGET_CHASE } , // If you fail, follow normally
{ TASK_MOVE_TO_TARGET_RANGE_SCARED , 128.0f } , // Move within 128 of target ent (client)
//{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED },
} ;
Schedule_t slFollowScared [ ] =
{
{
tlFollowScared ,
ARRAYSIZE ( tlFollowScared ) ,
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE ,
bits_SOUND_DANGER ,
" FollowScared "
} ,
} ;
Task_t tlFaceTargetScared [ ] =
{
{ TASK_FACE_TARGET , 0.0f } ,
{ TASK_SET_ACTIVITY , ( float ) ACT_CROUCHIDLE } ,
{ TASK_SET_SCHEDULE , ( float ) SCHED_TARGET_CHASE_SCARED } ,
} ;
Schedule_t slFaceTargetScared [ ] =
{
{
tlFaceTargetScared ,
ARRAYSIZE ( tlFaceTargetScared ) ,
bits_COND_HEAR_SOUND |
bits_COND_NEW_ENEMY ,
bits_SOUND_DANGER ,
" FaceTargetScared "
} ,
} ;
Task_t tlStopFollowing [ ] =
{
{ TASK_CANT_FOLLOW , 0.0f } ,
} ;
Schedule_t slStopFollowing [ ] =
{
{
tlStopFollowing ,
ARRAYSIZE ( tlStopFollowing ) ,
0 ,
0 ,
" StopFollowing "
} ,
} ;
Task_t tlHeal [ ] =
{
{ TASK_MOVE_TO_TARGET_RANGE , 50.0f } , // Move within 60 of target ent (client)
{ TASK_SET_FAIL_SCHEDULE , ( float ) SCHED_TARGET_CHASE } , // If you fail, catch up with that guy! (change this to put syringe away and then chase)
{ TASK_FACE_IDEAL , 0.0f } ,
{ TASK_SAY_HEAL , 0.0f } ,
{ TASK_PLAY_SEQUENCE_FACE_TARGET , ( float ) ACT_ARM } , // Whip out the needle
{ TASK_HEAL , 0.0f } , // Put it in the player
{ TASK_PLAY_SEQUENCE_FACE_TARGET , ( float ) ACT_DISARM } , // Put away the needle
} ;
Schedule_t slHeal [ ] =
{
{
tlHeal ,
ARRAYSIZE ( tlHeal ) ,
0 , // Don't interrupt or he'll end up running around with a needle all the time
0 ,
" Heal "
} ,
} ;
Task_t tlFaceTarget [ ] =
{
{ TASK_STOP_MOVING , 0.0f } ,
{ TASK_FACE_TARGET , 0.0f } ,
{ TASK_SET_ACTIVITY , ( float ) ACT_IDLE } ,
{ TASK_SET_SCHEDULE , ( float ) SCHED_TARGET_CHASE } ,
} ;
Schedule_t slFaceTarget [ ] =
{
{
tlFaceTarget ,
ARRAYSIZE ( tlFaceTarget ) ,
bits_COND_CLIENT_PUSH |
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND ,
bits_SOUND_COMBAT |
bits_SOUND_DANGER ,
" FaceTarget "
} ,
} ;
Task_t tlSciPanic [ ] =
{
{ TASK_STOP_MOVING , 0.0f } ,
{ TASK_FACE_ENEMY , 0.0f } ,
{ TASK_SCREAM , 0.0f } ,
{ TASK_PLAY_SEQUENCE_FACE_ENEMY , ( float ) ACT_EXCITED } , // This is really fear-stricken excitement
{ TASK_SET_ACTIVITY , ( float ) ACT_IDLE } ,
} ;
Schedule_t slSciPanic [ ] =
{
{
tlSciPanic ,
ARRAYSIZE ( tlSciPanic ) ,
0 ,
0 ,
" SciPanic "
} ,
} ;
Task_t tlIdleSciStand [ ] =
{
{ TASK_STOP_MOVING , 0 } ,
{ TASK_SET_ACTIVITY , ( float ) ACT_IDLE } ,
{ TASK_WAIT , 2.0f } , // repick IDLESTAND every two seconds.
{ TASK_TLK_HEADRESET , ( float ) 0 } , // reset head position
} ;
Schedule_t slIdleSciStand [ ] =
{
{
tlIdleSciStand ,
ARRAYSIZE ( tlIdleSciStand ) ,
bits_COND_NEW_ENEMY |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE |
bits_COND_HEAR_SOUND |
bits_COND_SMELL |
bits_COND_CLIENT_PUSH |
bits_COND_PROVOKED ,
bits_SOUND_COMBAT | // sound flags
//bits_SOUND_PLAYER |
//bits_SOUND_WORLD |
bits_SOUND_DANGER |
bits_SOUND_MEAT | // scents
bits_SOUND_CARCASS |
bits_SOUND_GARBAGE ,
" IdleSciStand "
} ,
} ;
Task_t tlScientistCover [ ] =
{
{ TASK_SET_FAIL_SCHEDULE , ( float ) SCHED_PANIC } , // If you fail, just panic!
{ TASK_STOP_MOVING , 0.0f } ,
{ TASK_FIND_COVER_FROM_ENEMY , 0.0f } ,
{ TASK_RUN_PATH_SCARED , 0.0f } ,
{ TASK_TURN_LEFT , 179.0f } ,
{ TASK_SET_SCHEDULE , ( float ) SCHED_HIDE } ,
} ;
Schedule_t slScientistCover [ ] =
{
{
tlScientistCover ,
ARRAYSIZE ( tlScientistCover ) ,
bits_COND_NEW_ENEMY ,
0 ,
" ScientistCover "
} ,
} ;
Task_t tlScientistHide [ ] =
{
{ TASK_SET_FAIL_SCHEDULE , ( float ) SCHED_PANIC } , // If you fail, just panic!
{ TASK_STOP_MOVING , 0.0f } ,
{ TASK_PLAY_SEQUENCE , ( float ) ACT_CROUCH } ,
{ TASK_SET_ACTIVITY , ( float ) ACT_CROUCHIDLE } , // FIXME: This looks lame
{ TASK_WAIT_RANDOM , 10.0f } ,
} ;
Schedule_t slScientistHide [ ] =
{
{
tlScientistHide ,
ARRAYSIZE ( tlScientistHide ) ,
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND |
bits_COND_SEE_ENEMY |
bits_COND_SEE_HATE |
bits_COND_SEE_FEAR |
bits_COND_SEE_DISLIKE ,
bits_SOUND_DANGER ,
" ScientistHide "
} ,
} ;
Task_t tlScientistStartle [ ] =
{
{ TASK_SET_FAIL_SCHEDULE , ( float ) SCHED_PANIC } , // If you fail, just panic!
{ TASK_RANDOM_SCREAM , 0.3f } , // Scream 30% of the time
{ TASK_STOP_MOVING , 0.0f } ,
{ TASK_PLAY_SEQUENCE_FACE_ENEMY , ( float ) ACT_CROUCH } ,
{ TASK_RANDOM_SCREAM , 0.1f } , // Scream again 10% of the time
{ TASK_PLAY_SEQUENCE_FACE_ENEMY , ( float ) ACT_CROUCHIDLE } ,
{ TASK_WAIT_RANDOM , 1.0f } ,
} ;
Schedule_t slScientistStartle [ ] =
{
{
tlScientistStartle ,
ARRAYSIZE ( tlScientistStartle ) ,
bits_COND_NEW_ENEMY |
bits_COND_SEE_ENEMY |
bits_COND_SEE_HATE |
bits_COND_SEE_FEAR |
bits_COND_SEE_DISLIKE ,
0 ,
" ScientistStartle "
} ,
} ;
Task_t tlFear [ ] =
{
{ TASK_STOP_MOVING , 0.0f } ,
{ TASK_FACE_ENEMY , 0.0f } ,
{ TASK_SAY_FEAR , 0.0f } ,
//{ TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY },
} ;
Schedule_t slFear [ ] =
{
{
tlFear ,
ARRAYSIZE ( tlFear ) ,
bits_COND_NEW_ENEMY ,
0 ,
" Fear "
} ,
} ;
DEFINE_CUSTOM_SCHEDULES ( CScientist )
{
slFollow ,
slFaceTarget ,
slIdleSciStand ,
slFear ,
slScientistCover ,
slScientistHide ,
slScientistStartle ,
slHeal ,
slStopFollowing ,
slSciPanic ,
slFollowScared ,
slFaceTargetScared ,
} ;
IMPLEMENT_CUSTOM_SCHEDULES ( CScientist , CTalkMonster )
void CScientist : : DeclineFollowing ( void )
{
Talk ( 10 ) ;
m_hTalkTarget = m_hEnemy ;
# ifdef MOBILE_HACKS
if ( FClassnameIs ( pev , " monster_rosenberg " ) )
PlaySentence ( " RO_POK " , 2 , VOL_NORM , ATTN_NORM ) ;
else
# endif // MOBILE_HACKS
PlaySentence ( " SC_POK " , 2 , VOL_NORM , ATTN_NORM ) ;
}
void CScientist : : Scream ( void )
{
if ( FOkToSpeak ( ) )
{
Talk ( 10 ) ;
m_hTalkTarget = m_hEnemy ;
# ifdef MOBILE_HACKS
if ( FClassnameIs ( pev , " monster_rosenberg " ) )
PlaySentence ( " RO_SCREAM " , RANDOM_FLOAT ( 3.0f , 6.0f ) , VOL_NORM , ATTN_NORM ) ;
else
# endif // MOBILE_HACKS
PlaySentence ( " SC_SCREAM " , RANDOM_FLOAT ( 3.0f , 6.0f ) , VOL_NORM , ATTN_NORM ) ;
}
}
Activity CScientist : : GetStoppedActivity ( void )
{
if ( m_hEnemy ! = 0 )
return ACT_EXCITED ;
return CTalkMonster : : GetStoppedActivity ( ) ;
}
void CScientist : : StartTask ( Task_t * pTask )
{
switch ( pTask - > iTask )
{
case TASK_SAY_HEAL :
//if( FOkToSpeak() )
Talk ( 2 ) ;
m_hTalkTarget = m_hTargetEnt ;
# ifdef MOBILE_HACKS
if ( g_iModType = = MOD_BSHIFT & & FClassnameIs ( pev , " monster_rosenberg " ) )
PlaySentence ( " RO_HEAL " , 2 , VOL_NORM , ATTN_IDLE ) ;
else
# endif // MOBILE_HACKS
PlaySentence ( " SC_HEAL " , 2 , VOL_NORM , ATTN_IDLE ) ;
TaskComplete ( ) ;
break ;
case TASK_SCREAM :
Scream ( ) ;
TaskComplete ( ) ;
break ;
case TASK_RANDOM_SCREAM :
if ( RANDOM_FLOAT ( 0.0f , 1.0f ) < pTask - > flData )
Scream ( ) ;
TaskComplete ( ) ;
break ;
case TASK_SAY_FEAR :
if ( FOkToSpeak ( ) )
{
Talk ( 2 ) ;
m_hTalkTarget = m_hEnemy ;
# ifdef MOBILE_HACKS
if ( g_iModType = = MOD_BSHIFT & & FClassnameIs ( pev , " monster_rosenberg " ) )
{
PlaySentence ( " RO_FEAR " , 5 , VOL_NORM , ATTN_NORM ) ;
}
else
# endif // MOBILE_HACKS
{
//The enemy can be null here. - Solokiller
//Discovered while testing the barnacle grapple on headcrabs with scientists in view.
if ( m_hEnemy ! = 0 & & m_hEnemy - > IsPlayer ( ) )
PlaySentence ( " SC_PLFEAR " , 5 , VOL_NORM , ATTN_NORM ) ;
else
PlaySentence ( " SC_FEAR " , 5 , VOL_NORM , ATTN_NORM ) ;
}
}
TaskComplete ( ) ;
break ;
case TASK_HEAL :
m_IdealActivity = ACT_MELEE_ATTACK1 ;
break ;
case TASK_RUN_PATH_SCARED :
m_movementActivity = ACT_RUN_SCARED ;
break ;
case TASK_MOVE_TO_TARGET_RANGE_SCARED :
{
if ( ( m_hTargetEnt - > pev - > origin - pev - > origin ) . Length ( ) < 1.0f )
{
TaskComplete ( ) ;
}
else
{
m_vecMoveGoal = m_hTargetEnt - > pev - > origin ;
if ( ! MoveToTarget ( ACT_WALK_SCARED , 0.5 ) )
TaskFail ( ) ;
}
}
break ;
default :
CTalkMonster : : StartTask ( pTask ) ;
break ;
}
}
void CScientist : : RunTask ( Task_t * pTask )
{
switch ( pTask - > iTask )
{
case TASK_RUN_PATH_SCARED :
if ( MovementIsComplete ( ) )
TaskComplete ( ) ;
if ( RANDOM_LONG ( 0 , 31 ) < 8 )
Scream ( ) ;
break ;
case TASK_MOVE_TO_TARGET_RANGE_SCARED :
{
if ( RANDOM_LONG ( 0 , 63 ) < 8 )
Scream ( ) ;
if ( m_hEnemy = = 0 )
{
TaskFail ( ) ;
}
else
{
float distance ;
distance = ( m_vecMoveGoal - pev - > origin ) . Length2D ( ) ;
// Re-evaluate when you think your finished, or the target has moved too far
if ( ( distance < pTask - > flData ) | | ( m_vecMoveGoal - m_hTargetEnt - > pev - > origin ) . Length ( ) > pTask - > flData * 0.5f )
{
m_vecMoveGoal = m_hTargetEnt - > pev - > origin ;
distance = ( m_vecMoveGoal - pev - > origin ) . Length2D ( ) ;
FRefreshRoute ( ) ;
}
// Set the appropriate activity based on an overlapping range
// overlap the range to prevent oscillation
if ( distance < pTask - > flData )
{
TaskComplete ( ) ;
RouteClear ( ) ; // Stop moving
}
else if ( distance < 190 & & m_movementActivity ! = ACT_WALK_SCARED )
m_movementActivity = ACT_WALK_SCARED ;
else if ( distance > = 270 & & m_movementActivity ! = ACT_RUN_SCARED )
m_movementActivity = ACT_RUN_SCARED ;
}
}
break ;
case TASK_HEAL :
if ( m_fSequenceFinished )
{
TaskComplete ( ) ;
}
else
{
if ( TargetDistance ( ) > 90 )
TaskComplete ( ) ;
pev - > ideal_yaw = UTIL_VecToYaw ( m_hTargetEnt - > pev - > origin - pev - > origin ) ;
ChangeYaw ( pev - > yaw_speed ) ;
}
break ;
default :
CTalkMonster : : RunTask ( pTask ) ;
break ;
}
}
//=========================================================
// Classify - indicates this monster's place in the
// relationship table.
//=========================================================
int CScientist : : Classify ( void )
{
return CLASS_HUMAN_PASSIVE ;
}
//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
void CScientist : : SetYawSpeed ( void )
{
int ys ;
ys = 90 ;
switch ( m_Activity )
{
case ACT_IDLE :
ys = 120 ;
break ;
case ACT_WALK :
ys = 180 ;
break ;
case ACT_RUN :
ys = 150 ;
break ;
case ACT_TURN_LEFT :
case ACT_TURN_RIGHT :
ys = 120 ;
break ;
default :
break ;
}
pev - > yaw_speed = ys ;
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CScientist : : HandleAnimEvent ( MonsterEvent_t * pEvent )
{
switch ( pEvent - > event )
{
case SCIENTIST_AE_HEAL : // Heal my target (if within range)
Heal ( ) ;
break ;
case SCIENTIST_AE_NEEDLEON :
{
int oldBody = pev - > body ;
pev - > body = ( oldBody % NUM_SCIENTIST_HEADS ) + NUM_SCIENTIST_HEADS * 1 ;
}
break ;
case SCIENTIST_AE_NEEDLEOFF :
{
int oldBody = pev - > body ;
pev - > body = ( oldBody % NUM_SCIENTIST_HEADS ) + NUM_SCIENTIST_HEADS * 0 ;
}
break ;
default :
CTalkMonster : : HandleAnimEvent ( pEvent ) ;
}
}
//=========================================================
// Spawn
//=========================================================
void CScientist : : Spawn ( void )
{
Precache ( ) ;
SET_MODEL ( ENT ( pev ) , " models/scientist.mdl " ) ;
UTIL_SetSize ( pev , VEC_HUMAN_HULL_MIN , VEC_HUMAN_HULL_MAX ) ;
pev - > solid = SOLID_SLIDEBOX ;
pev - > movetype = MOVETYPE_STEP ;
m_bloodColor = BLOOD_COLOR_RED ;
# ifdef MOBILE_HACKS
if ( g_iModType = = MOD_BSHIFT & & FClassnameIs ( pev , " monster_rosenberg " ) )
pev - > health = gSkillData . scientistHealth * 2 ;
else
# endif // MOBILE_HACKS
pev - > health = gSkillData . scientistHealth ;
pev - > view_ofs = Vector ( 0 , 0 , 50 ) ; // position of the eyes relative to monster's origin.
m_flFieldOfView = VIEW_FIELD_WIDE ; // NOTE: we need a wide field of view so scientists will notice player and say hello
m_MonsterState = MONSTERSTATE_NONE ;
//m_flDistTooFar = 256.0;
m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE ;
// White hands
pev - > skin = 0 ;
if ( pev - > body = = - 1 )
{
// -1 chooses a random head
pev - > body = RANDOM_LONG ( 0 , NUM_SCIENTIST_HEADS - 1 ) ; // pick a head, any head
}
// Luther is black, make his hands black
if ( pev - > body = = HEAD_LUTHER )
pev - > skin = 1 ;
MonsterInit ( ) ;
SetUse ( & CTalkMonster : : FollowerUse ) ;
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CScientist : : Precache ( void )
{
PRECACHE_MODEL ( " models/scientist.mdl " ) ;
# ifdef MOBILE_HACKS
if ( g_iModType ! = MOD_BSHIFT | | ! FClassnameIs ( pev , " monster_rosenberg " ) )
# endif // MOBILE_HACKS
{
PRECACHE_SOUND ( " scientist/sci_pain1.wav " ) ;
PRECACHE_SOUND ( " scientist/sci_pain2.wav " ) ;
PRECACHE_SOUND ( " scientist/sci_pain3.wav " ) ;
PRECACHE_SOUND ( " scientist/sci_pain4.wav " ) ;
PRECACHE_SOUND ( " scientist/sci_pain5.wav " ) ;
}
# ifdef MOBILE_HACKS
else
{
PRECACHE_SOUND ( " rosenberg/ro_pain0.wav " ) ;
PRECACHE_SOUND ( " rosenberg/ro_pain1.wav " ) ;
PRECACHE_SOUND ( " rosenberg/ro_pain2.wav " ) ;
PRECACHE_SOUND ( " rosenberg/ro_pain3.wav " ) ;
PRECACHE_SOUND ( " rosenberg/ro_pain4.wav " ) ;
PRECACHE_SOUND ( " rosenberg/ro_pain5.wav " ) ;
PRECACHE_SOUND ( " rosenberg/ro_pain6.wav " ) ;
PRECACHE_SOUND ( " rosenberg/ro_pain7.wav " ) ;
PRECACHE_SOUND ( " rosenberg/ro_pain8.wav " ) ;
}
# endif // MOBILE_HACKS
// every new scientist must call this, otherwise
// when a level is loaded, nobody will talk (time is reset to 0)
TalkInit ( ) ;
CTalkMonster : : Precache ( ) ;
}
// Init talk data
void CScientist : : TalkInit ( )
{
CTalkMonster : : TalkInit ( ) ;
// scientists speach group names (group names are in sentences.txt)
# ifdef MOBILE_HACKS
if ( g_iModType = = MOD_BSHIFT & & FClassnameIs ( pev , " monster_rosenberg " ) )
{
m_szGrp [ TLK_ANSWER ] = " RO_ANSWER " ;
m_szGrp [ TLK_QUESTION ] = " RO_QUESTION " ;
m_szGrp [ TLK_IDLE ] = " RO_IDLE " ;
m_szGrp [ TLK_STARE ] = " RO_STARE " ;
m_szGrp [ TLK_USE ] = " RO_OK " ;
m_szGrp [ TLK_UNUSE ] = " RO_WAIT " ;
m_szGrp [ TLK_STOP ] = " RO_STOP " ;
m_szGrp [ TLK_NOSHOOT ] = " RO_SCARED " ;
m_szGrp [ TLK_HELLO ] = " RO_HELLO " ;
m_szGrp [ TLK_PLHURT1 ] = " !RO_CUREA " ;
m_szGrp [ TLK_PLHURT2 ] = " !RO_CUREB " ;
m_szGrp [ TLK_PLHURT3 ] = " !RO_CUREC " ;
m_szGrp [ TLK_PHELLO ] = " RO_PHELLO " ;
m_szGrp [ TLK_PIDLE ] = " RO_PIDLE " ;
m_szGrp [ TLK_PQUESTION ] = " RO_PQUEST " ;
m_szGrp [ TLK_SMELL ] = " RO_SMELL " ;
m_szGrp [ TLK_WOUND ] = " RO_WOUND " ;
m_szGrp [ TLK_MORTAL ] = " RO_MORTAL " ;
}
else
# endif // MOBILE_HACKS
{
m_szGrp [ TLK_ANSWER ] = " SC_ANSWER " ;
m_szGrp [ TLK_QUESTION ] = " SC_QUESTION " ;
m_szGrp [ TLK_IDLE ] = " SC_IDLE " ;
m_szGrp [ TLK_STARE ] = " SC_STARE " ;
m_szGrp [ TLK_USE ] = " SC_OK " ;
m_szGrp [ TLK_UNUSE ] = " SC_WAIT " ;
m_szGrp [ TLK_STOP ] = " SC_STOP " ;
m_szGrp [ TLK_NOSHOOT ] = " SC_SCARED " ;
m_szGrp [ TLK_HELLO ] = " SC_HELLO " ;
m_szGrp [ TLK_PLHURT1 ] = " !SC_CUREA " ;
m_szGrp [ TLK_PLHURT2 ] = " !SC_CUREB " ;
m_szGrp [ TLK_PLHURT3 ] = " !SC_CUREC " ;
m_szGrp [ TLK_PHELLO ] = " SC_PHELLO " ;
m_szGrp [ TLK_PIDLE ] = " SC_PIDLE " ;
m_szGrp [ TLK_PQUESTION ] = " SC_PQUEST " ;
m_szGrp [ TLK_SMELL ] = " SC_SMELL " ;
m_szGrp [ TLK_WOUND ] = " SC_WOUND " ;
m_szGrp [ TLK_MORTAL ] = " SC_MORTAL " ;
}
// get voice for head
switch ( pev - > body % 3 )
{
default :
case HEAD_GLASSES :
m_voicePitch = 105 ;
break ; //glasses
case HEAD_EINSTEIN :
m_voicePitch = 100 ;
break ; //einstein
case HEAD_LUTHER :
m_voicePitch = 95 ;
break ; //luther
case HEAD_SLICK :
m_voicePitch = 100 ;
break ; //slick
}
}
int CScientist : : TakeDamage ( entvars_t * pevInflictor , entvars_t * pevAttacker , float flDamage , int bitsDamageType )
{
if ( pevInflictor & & pevInflictor - > flags & FL_CLIENT )
{
# ifdef MOBILE_HACKS
if ( g_iModType ! = MOD_BSHIFT & & ! FClassnameIs ( pev , " monster_rosenberg " ) )
# endif // MOBILE_HACKS
{
Remember ( bits_MEMORY_PROVOKED ) ;
StopFollowing ( TRUE ) ;
}
}
// make sure friends talk about it if player hurts scientist...
return CTalkMonster : : TakeDamage ( pevInflictor , pevAttacker , flDamage , bitsDamageType ) ;
}
//=========================================================
// ISoundMask - returns a bit mask indicating which types
// of sounds this monster regards. In the base class implementation,
// monsters care about all sounds, but no scents.
//=========================================================
int CScientist : : ISoundMask ( void )
{
return bits_SOUND_WORLD |
bits_SOUND_COMBAT |
bits_SOUND_CARCASS |
bits_SOUND_MEAT |
bits_SOUND_GARBAGE |
bits_SOUND_DANGER |
bits_SOUND_PLAYER ;
}
//=========================================================
// PainSound
//=========================================================
void CScientist : : PainSound ( void )
{
const char * pszSound ;
if ( gpGlobals - > time < m_painTime )
return ;
m_painTime = gpGlobals - > time + RANDOM_FLOAT ( 0.5f , 0.75f ) ;
# ifdef MOBILE_HACKS
if ( g_iModType = = MOD_BSHIFT & & FClassnameIs ( pev , " monster_rosenberg " ) )
switch ( RANDOM_LONG ( 0 , 8 ) )
{
case 0 :
pszSound = " rosenberg/ro_pain0.wav " ;
break ;
case 1 :
pszSound = " rosenberg/ro_pain1.wav " ;
break ;
case 2 :
pszSound = " rosenberg/ro_pain2.wav " ;
break ;
case 3 :
pszSound = " rosenberg/ro_pain3.wav " ;
break ;
case 4 :
pszSound = " rosenberg/ro_pain4.wav " ;
break ;
case 5 :
pszSound = " rosenberg/ro_pain5.wav " ;
break ;
case 6 :
pszSound = " rosenberg/ro_pain6.wav " ;
break ;
case 7 :
pszSound = " rosenberg/ro_pain7.wav " ;
break ;
case 8 :
pszSound = " rosenberg/ro_pain8.wav " ;
break ;
}
else
# endif // MOBILE_HACKS
switch ( RANDOM_LONG ( 0 , 4 ) )
{
case 0 :
pszSound = " scientist/sci_pain1.wav " ;
break ;
case 1 :
pszSound = " scientist/sci_pain2.wav " ;
break ;
case 2 :
pszSound = " scientist/sci_pain3.wav " ;
break ;
case 3 :
pszSound = " scientist/sci_pain4.wav " ;
break ;
case 4 :
pszSound = " scientist/sci_pain5.wav " ;
break ;
}
EMIT_SOUND_DYN ( ENT ( pev ) , CHAN_VOICE , pszSound , 1 , ATTN_NORM , 0 , GetVoicePitch ( ) ) ;
}
//=========================================================
// DeathSound
//=========================================================
void CScientist : : DeathSound ( void )
{
PainSound ( ) ;
}
void CScientist : : Killed ( entvars_t * pevAttacker , int iGib )
{
SetUse ( NULL ) ;
CTalkMonster : : Killed ( pevAttacker , iGib ) ;
}
void CScientist : : SetActivity ( Activity newActivity )
{
int iSequence ;
iSequence = LookupActivity ( newActivity ) ;
// Set to the desired anim, or default anim if the desired is not present
if ( iSequence = = ACTIVITY_NOT_AVAILABLE )
newActivity = ACT_IDLE ;
CTalkMonster : : SetActivity ( newActivity ) ;
}
Schedule_t * CScientist : : GetScheduleOfType ( int Type )
{
Schedule_t * psched ;
switch ( Type )
{
// Hook these to make a looping schedule
case SCHED_TARGET_FACE :
// call base class default so that scientist will talk
// when 'used'
psched = CTalkMonster : : GetScheduleOfType ( Type ) ;
if ( psched = = slIdleStand )
return slFaceTarget ; // override this for different target face behavior
else
return psched ;
case SCHED_TARGET_CHASE :
return slFollow ;
case SCHED_CANT_FOLLOW :
return slStopFollowing ;
case SCHED_PANIC :
return slSciPanic ;
case SCHED_TARGET_CHASE_SCARED :
return slFollowScared ;
case SCHED_TARGET_FACE_SCARED :
return slFaceTargetScared ;
case SCHED_IDLE_STAND :
// call base class default so that scientist will talk
// when standing during idle
psched = CTalkMonster : : GetScheduleOfType ( Type ) ;
if ( psched = = slIdleStand )
return slIdleSciStand ;
else
return psched ;
case SCHED_HIDE :
return slScientistHide ;
case SCHED_STARTLE :
return slScientistStartle ;
case SCHED_FEAR :
return slFear ;
}
return CTalkMonster : : GetScheduleOfType ( Type ) ;
}
Schedule_t * CScientist : : GetSchedule ( void )
{
// so we don't keep calling through the EHANDLE stuff
CBaseEntity * pEnemy = m_hEnemy ;
if ( HasConditions ( bits_COND_HEAR_SOUND ) )
{
CSound * pSound ;
pSound = PBestSound ( ) ;
ASSERT ( pSound ! = NULL ) ;
if ( pSound & & ( pSound - > m_iType & bits_SOUND_DANGER ) )
return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_BEST_SOUND ) ;
}
switch ( m_MonsterState )
{
case MONSTERSTATE_ALERT :
case MONSTERSTATE_IDLE :
if ( pEnemy )
{
if ( HasConditions ( bits_COND_SEE_ENEMY ) )
m_fearTime = gpGlobals - > time ;
else if ( DisregardEnemy ( pEnemy ) ) // After 15 seconds of being hidden, return to alert
{
m_hEnemy = 0 ;
pEnemy = 0 ;
}
}
if ( HasConditions ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) )
{
// flinch if hurt
return GetScheduleOfType ( SCHED_SMALL_FLINCH ) ;
}
// Cower when you hear something scary
if ( HasConditions ( bits_COND_HEAR_SOUND ) )
{
CSound * pSound ;
pSound = PBestSound ( ) ;
ASSERT ( pSound ! = NULL ) ;
if ( pSound )
{
if ( pSound - > m_iType & ( bits_SOUND_DANGER | bits_SOUND_COMBAT ) )
{
if ( gpGlobals - > time - m_fearTime > 3 ) // Only cower every 3 seconds or so
{
m_fearTime = gpGlobals - > time ; // Update last fear
return GetScheduleOfType ( SCHED_STARTLE ) ; // This will just duck for a second
}
}
}
}
// Behavior for following the player
if ( IsFollowing ( ) )
{
if ( ! m_hTargetEnt - > IsAlive ( ) )
{
// UNDONE: Comment about the recently dead player here?
StopFollowing ( FALSE ) ;
break ;
}
int relationship = R_NO ;
// Nothing scary, just me and the player
if ( pEnemy ! = NULL )
relationship = IRelationship ( pEnemy ) ;
// UNDONE: Model fear properly, fix R_FR and add multiple levels of fear
if ( relationship ! = R_DL & & relationship ! = R_HT )
{
// If I'm already close enough to my target
if ( TargetDistance ( ) < = 128 )
{
if ( CanHeal ( ) ) // Heal opportunistically
return slHeal ;
if ( HasConditions ( bits_COND_CLIENT_PUSH ) ) // Player wants me to move
return GetScheduleOfType ( SCHED_MOVE_AWAY_FOLLOW ) ;
}
return GetScheduleOfType ( SCHED_TARGET_FACE ) ; // Just face and follow.
}
# ifdef MOBILE_HACKS
else if ( g_iModType ! = MOD_BSHIFT | | ! FClassnameIs ( pev , " monster_rosenberg " ) ) // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared
# else // MOBILE_HACKS
else
# endif // MOBILE_HACKS
{
if ( HasConditions ( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react
return GetScheduleOfType ( SCHED_FEAR ) ; // React to something scary
return GetScheduleOfType ( SCHED_TARGET_FACE_SCARED ) ; // face and follow, but I'm scared!
}
}
if ( HasConditions ( bits_COND_CLIENT_PUSH ) ) // Player wants me to move
return GetScheduleOfType ( SCHED_MOVE_AWAY ) ;
// try to say something about smells
TrySmellTalk ( ) ;
break ;
case MONSTERSTATE_COMBAT :
if ( HasConditions ( bits_COND_NEW_ENEMY ) )
return slFear ; // Point and scream!
if ( HasConditions ( bits_COND_SEE_ENEMY ) )
return slScientistCover ; // Take Cover
if ( HasConditions ( bits_COND_HEAR_SOUND ) )
return slTakeCoverFromBestSound ; // Cower and panic from the scary sound!
return slScientistCover ; // Run & Cower
break ;
default :
break ;
}
return CTalkMonster : : GetSchedule ( ) ;
}
MONSTERSTATE CScientist : : GetIdealState ( void )
{
switch ( m_MonsterState )
{
case MONSTERSTATE_ALERT :
case MONSTERSTATE_IDLE :
if ( HasConditions ( bits_COND_NEW_ENEMY ) )
{
if ( IsFollowing ( ) )
{
int relationship = IRelationship ( m_hEnemy ) ;
if ( relationship ! = R_FR | | ( relationship ! = R_HT & & ! HasConditions ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) )
{
// Don't go to combat if you're following the player
m_IdealMonsterState = MONSTERSTATE_ALERT ;
return m_IdealMonsterState ;
}
StopFollowing ( TRUE ) ;
}
}
else if ( HasConditions ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) )
{
// Stop following if you take damage
if ( IsFollowing ( ) )
StopFollowing ( TRUE ) ;
}
break ;
case MONSTERSTATE_COMBAT :
{
CBaseEntity * pEnemy = m_hEnemy ;
if ( pEnemy ! = NULL )
{
if ( DisregardEnemy ( pEnemy ) ) // After 15 seconds of being hidden, return to alert
{
// Strip enemy when going to alert
m_IdealMonsterState = MONSTERSTATE_ALERT ;
m_hEnemy = 0 ;
return m_IdealMonsterState ;
}
// Follow if only scared a little
if ( m_hTargetEnt ! = 0 )
{
m_IdealMonsterState = MONSTERSTATE_ALERT ;
return m_IdealMonsterState ;
}
if ( HasConditions ( bits_COND_SEE_ENEMY ) )
{
m_fearTime = gpGlobals - > time ;
m_IdealMonsterState = MONSTERSTATE_COMBAT ;
return m_IdealMonsterState ;
}
}
}
break ;
default :
break ;
}
return CTalkMonster : : GetIdealState ( ) ;
}
BOOL CScientist : : CanHeal ( void )
{
if ( ( m_healTime > gpGlobals - > time ) | | ( m_hTargetEnt = = 0 ) | | ( m_hTargetEnt - > pev - > health > ( m_hTargetEnt - > pev - > max_health * 0.5f ) ) )
return FALSE ;
return TRUE ;
}
void CScientist : : Heal ( void )
{
if ( ! CanHeal ( ) )
return ;
Vector target = m_hTargetEnt - > pev - > origin - pev - > origin ;
if ( target . Length ( ) > 100.0f )
return ;
m_hTargetEnt - > TakeHealth ( gSkillData . scientistHeal , DMG_GENERIC ) ;
// Don't heal again for 1 minute
m_healTime = gpGlobals - > time + 60 ;
}
int CScientist : : FriendNumber ( int arrayNumber )
{
static int array [ 3 ] = { 1 , 2 , 0 } ;
if ( arrayNumber < 3 )
return array [ arrayNumber ] ;
return arrayNumber ;
}
//=========================================================
// Dead Scientist PROP
//=========================================================
const char * CDeadScientist : : m_szPoses [ ] =
{
" lying_on_back " ,
" lying_on_stomach " ,
" dead_sitting " ,
" dead_hang " ,
" dead_table1 " ,
" dead_table2 " ,
" dead_table3 "
} ;
void CDeadScientist : : KeyValue ( KeyValueData * pkvd )
{
if ( FStrEq ( pkvd - > szKeyName , " pose " ) )
{
m_iPose = atoi ( pkvd - > szValue ) ;
pkvd - > fHandled = TRUE ;
}
else
CBaseMonster : : KeyValue ( pkvd ) ;
}
LINK_ENTITY_TO_CLASS ( monster_scientist_dead , CDeadScientist )
//
// ********** DeadScientist SPAWN **********
//
void CDeadScientist : : Spawn ( )
{
PRECACHE_MODEL ( " models/scientist.mdl " ) ;
SET_MODEL ( ENT ( pev ) , " models/scientist.mdl " ) ;
pev - > effects = 0 ;
pev - > sequence = 0 ;
// Corpses have less health
pev - > health = 8 ; //gSkillData.scientistHealth;
m_bloodColor = BLOOD_COLOR_RED ;
if ( pev - > body = = - 1 )
{
// -1 chooses a random head
pev - > body = RANDOM_LONG ( 0 , NUM_SCIENTIST_HEADS - 1 ) ; // pick a head, any head
}
// Luther is black, make his hands black
if ( pev - > body = = HEAD_LUTHER )
pev - > skin = 1 ;
else
pev - > skin = 0 ;
pev - > sequence = LookupSequence ( m_szPoses [ m_iPose ] ) ;
if ( pev - > sequence = = - 1 )
{
ALERT ( at_console , " Dead scientist with bad pose \n " ) ;
}
// pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again!
MonsterInitDead ( ) ;
}
//=========================================================
// Sitting Scientist PROP
//=========================================================
class CSittingScientist : public CScientist // kdb: changed from public CBaseMonster so he can speak
{
public :
void Spawn ( void ) ;
void Precache ( void ) ;
void EXPORT SittingThink ( void ) ;
int Classify ( void ) ;
virtual int Save ( CSave & save ) ;
virtual int Restore ( CRestore & restore ) ;
static TYPEDESCRIPTION m_SaveData [ ] ;
virtual void SetAnswerQuestion ( CTalkMonster * pSpeaker ) ;
int FriendNumber ( int arrayNumber ) ;
int FIdleSpeak ( void ) ;
int m_baseSequence ;
int m_headTurn ;
float m_flResponseDelay ;
} ;
LINK_ENTITY_TO_CLASS ( monster_sitting_scientist , CSittingScientist )
TYPEDESCRIPTION CSittingScientist : : m_SaveData [ ] =
{
// Don't need to save/restore m_baseSequence (recalced)
DEFINE_FIELD ( CSittingScientist , m_headTurn , FIELD_INTEGER ) ,
DEFINE_FIELD ( CSittingScientist , m_flResponseDelay , FIELD_FLOAT ) ,
} ;
IMPLEMENT_SAVERESTORE ( CSittingScientist , CScientist )
// animation sequence aliases
typedef enum
{
SITTING_ANIM_sitlookleft ,
SITTING_ANIM_sitlookright ,
SITTING_ANIM_sitscared ,
SITTING_ANIM_sitting2 ,
SITTING_ANIM_sitting3
} SITTING_ANIM ;
//
// ********** Scientist SPAWN **********
//
void CSittingScientist : : Spawn ( )
{
PRECACHE_MODEL ( " models/scientist.mdl " ) ;
SET_MODEL ( ENT ( pev ) , " models/scientist.mdl " ) ;
Precache ( ) ;
InitBoneControllers ( ) ;
UTIL_SetSize ( pev , Vector ( - 14 , - 14 , 0 ) , Vector ( 14 , 14 , 36 ) ) ;
pev - > solid = SOLID_SLIDEBOX ;
pev - > movetype = MOVETYPE_STEP ;
pev - > effects = 0 ;
pev - > health = 50 ;
m_bloodColor = BLOOD_COLOR_RED ;
m_flFieldOfView = VIEW_FIELD_WIDE ; // indicates the width of this monster's forward view cone ( as a dotproduct result )
m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD ;
SetBits ( pev - > spawnflags , SF_MONSTER_PREDISASTER ) ; // predisaster only!
if ( pev - > body = = - 1 )
{
// -1 chooses a random head
pev - > body = RANDOM_LONG ( 0 , NUM_SCIENTIST_HEADS - 1 ) ; // pick a head, any head
}
// Luther is black, make his hands black
if ( pev - > body = = HEAD_LUTHER )
pev - > skin = 1 ;
m_baseSequence = LookupSequence ( " sitlookleft " ) ;
pev - > sequence = m_baseSequence + RANDOM_LONG ( 0 , 4 ) ;
ResetSequenceInfo ( ) ;
SetThink ( & CSittingScientist : : SittingThink ) ;
pev - > nextthink = gpGlobals - > time + 0.1f ;
DROP_TO_FLOOR ( ENT ( pev ) ) ;
}
void CSittingScientist : : Precache ( void )
{
m_baseSequence = LookupSequence ( " sitlookleft " ) ;
TalkInit ( ) ;
}
//=========================================================
// ID as a passive human
//=========================================================
int CSittingScientist : : Classify ( void )
{
return CLASS_HUMAN_PASSIVE ;
}
int CSittingScientist : : FriendNumber ( int arrayNumber )
{
static int array [ 3 ] = { 2 , 1 , 0 } ;
if ( arrayNumber < 3 )
return array [ arrayNumber ] ;
return arrayNumber ;
}
//=========================================================
// sit, do stuff
//=========================================================
void CSittingScientist : : SittingThink ( void )
{
CBaseEntity * pent ;
StudioFrameAdvance ( ) ;
// try to greet player
if ( FIdleHello ( ) )
{
pent = FindNearestFriend ( TRUE ) ;
if ( pent )
{
float yaw = VecToYaw ( pent - > pev - > origin - pev - > origin ) - pev - > angles . y ;
if ( yaw > 180 )
yaw - = 360 ;
if ( yaw < - 180 )
yaw + = 360 ;
if ( yaw > 0 )
pev - > sequence = m_baseSequence + SITTING_ANIM_sitlookleft ;
else
pev - > sequence = m_baseSequence + SITTING_ANIM_sitlookright ;
ResetSequenceInfo ( ) ;
pev - > frame = 0 ;
SetBoneController ( 0 , 0 ) ;
}
}
else if ( m_fSequenceFinished )
{
int i = RANDOM_LONG ( 0 , 99 ) ;
m_headTurn = 0 ;
if ( m_flResponseDelay & & gpGlobals - > time > m_flResponseDelay )
{
// respond to question
IdleRespond ( ) ;
pev - > sequence = m_baseSequence + SITTING_ANIM_sitscared ;
m_flResponseDelay = 0 ;
}
else if ( i < 30 )
{
pev - > sequence = m_baseSequence + SITTING_ANIM_sitting3 ;
// turn towards player or nearest friend and speak
if ( ! FBitSet ( m_bitsSaid , bit_saidHelloPlayer ) )
pent = FindNearestFriend ( TRUE ) ;
else
pent = FindNearestFriend ( FALSE ) ;
if ( ! FIdleSpeak ( ) | | ! pent )
{
m_headTurn = RANDOM_LONG ( 0 , 8 ) * 10 - 40 ;
pev - > sequence = m_baseSequence + SITTING_ANIM_sitting3 ;
}
else
{
// only turn head if we spoke
float yaw = VecToYaw ( pent - > pev - > origin - pev - > origin ) - pev - > angles . y ;
if ( yaw > 180 )
yaw - = 360 ;
if ( yaw < - 180 )
yaw + = 360 ;
if ( yaw > 0 )
pev - > sequence = m_baseSequence + SITTING_ANIM_sitlookleft ;
else
pev - > sequence = m_baseSequence + SITTING_ANIM_sitlookright ;
//ALERT( at_console, "sitting speak\n" );
}
}
else if ( i < 60 )
{
pev - > sequence = m_baseSequence + SITTING_ANIM_sitting3 ;
m_headTurn = RANDOM_LONG ( 0 , 8 ) * 10 - 40 ;
if ( RANDOM_LONG ( 0 , 99 ) < 5 )
{
//ALERT( at_console, "sitting speak2\n" );
FIdleSpeak ( ) ;
}
}
else if ( i < 80 )
{
pev - > sequence = m_baseSequence + SITTING_ANIM_sitting2 ;
}
else if ( i < 100 )
{
pev - > sequence = m_baseSequence + SITTING_ANIM_sitscared ;
}
ResetSequenceInfo ( ) ;
pev - > frame = 0 ;
SetBoneController ( 0 , m_headTurn ) ;
}
pev - > nextthink = gpGlobals - > time + 0.1f ;
}
// prepare sitting scientist to answer a question
void CSittingScientist : : SetAnswerQuestion ( CTalkMonster * pSpeaker )
{
m_flResponseDelay = gpGlobals - > time + RANDOM_FLOAT ( 3.0f , 4.0f ) ;
m_hTalkTarget = ( CBaseMonster * ) pSpeaker ;
}
//=========================================================
// FIdleSpeak
// ask question of nearby friend, or make statement
//=========================================================
int CSittingScientist : : FIdleSpeak ( void )
{
// try to start a conversation, or make statement
int pitch ;
if ( ! FOkToSpeak ( ) )
return FALSE ;
// set global min delay for next conversation
CTalkMonster : : g_talkWaitTime = gpGlobals - > time + RANDOM_FLOAT ( 4.8 , 5.2 ) ;
pitch = GetVoicePitch ( ) ;
// 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 ) ;
if ( pentFriend & & RANDOM_LONG ( 0 , 1 ) )
{
CTalkMonster * pTalkMonster = GetClassPtr ( ( CTalkMonster * ) pentFriend - > pev ) ;
pTalkMonster - > SetAnswerQuestion ( this ) ;
IdleHeadTurn ( pentFriend - > pev - > origin ) ;
SENTENCEG_PlayRndSz ( ENT ( pev ) , m_szGrp [ TLK_PQUESTION ] , 1.0 , ATTN_IDLE , 0 , pitch ) ;
// set global min delay for next conversation
CTalkMonster : : g_talkWaitTime = gpGlobals - > time + RANDOM_FLOAT ( 4.8 , 5.2 ) ;
return TRUE ;
}
// otherwise, play an idle statement
if ( RANDOM_LONG ( 0 , 1 ) )
{
SENTENCEG_PlayRndSz ( ENT ( pev ) , m_szGrp [ TLK_PIDLE ] , 1.0 , ATTN_IDLE , 0 , pitch ) ;
// set global min delay for next conversation
CTalkMonster : : g_talkWaitTime = gpGlobals - > time + RANDOM_FLOAT ( 4.8 , 5.2 ) ;
return TRUE ;
}
// never spoke
CTalkMonster : : g_talkWaitTime = 0 ;
return FALSE ;
}