Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1320 lines
32 KiB

9 years ago
/***
*
* 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"
9 years ago
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
8 years ago
#define SCIENTIST_AE_HEAL ( 1 )
9 years ago
#define SCIENTIST_AE_NEEDLEON ( 2 )
#define SCIENTIST_AE_NEEDLEOFF ( 3 )
//=======================================================
// Scientist
//=======================================================
LINK_ENTITY_TO_CLASS( monster_scientist, CScientist )
9 years ago
TYPEDESCRIPTION CScientist::m_SaveData[] =
9 years ago
{
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 )
9 years ago
//=========================================================
// AI Schedules Specific to this monster
//=========================================================
Task_t tlFollow[] =
{
8 years ago
{ 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)
8 years ago
//{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE },
9 years ago
};
8 years ago
Schedule_t slFollow[] =
9 years ago
{
{
tlFollow,
8 years ago
ARRAYSIZE( tlFollow ),
9 years ago
bits_COND_NEW_ENEMY |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE |
bits_COND_HEAR_SOUND,
bits_SOUND_COMBAT |
bits_SOUND_DANGER,
"Follow"
},
};
8 years ago
Task_t tlFollowScared[] =
9 years ago
{
8 years ago
{ 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)
8 years ago
//{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED },
9 years ago
};
8 years ago
Schedule_t slFollowScared[] =
9 years ago
{
{
tlFollowScared,
8 years ago
ARRAYSIZE( tlFollowScared ),
9 years ago
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE,
bits_SOUND_DANGER,
"FollowScared"
},
};
8 years ago
Task_t tlFaceTargetScared[] =
9 years ago
{
{ TASK_FACE_TARGET, 0.0f },
8 years ago
{ TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE },
{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED },
9 years ago
};
8 years ago
Schedule_t slFaceTargetScared[] =
9 years ago
{
{
tlFaceTargetScared,
8 years ago
ARRAYSIZE( tlFaceTargetScared ),
9 years ago
bits_COND_HEAR_SOUND |
bits_COND_NEW_ENEMY,
bits_SOUND_DANGER,
"FaceTargetScared"
},
};
8 years ago
Task_t tlStopFollowing[] =
9 years ago
{
{ TASK_CANT_FOLLOW, 0.0f },
9 years ago
};
8 years ago
Schedule_t slStopFollowing[] =
9 years ago
{
{
tlStopFollowing,
8 years ago
ARRAYSIZE( tlStopFollowing ),
9 years ago
0,
0,
"StopFollowing"
},
};
8 years ago
Task_t tlHeal[] =
9 years ago
{
{ TASK_MOVE_TO_TARGET_RANGE, 50.0f }, // Move within 60 of target ent (client)
8 years ago
{ 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 },
8 years ago
{ TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM }, // Whip out the needle
{ TASK_HEAL, 0.0f }, // Put it in the player
8 years ago
{ TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM }, // Put away the needle
9 years ago
};
8 years ago
Schedule_t slHeal[] =
9 years ago
{
{
tlHeal,
8 years ago
ARRAYSIZE( tlHeal ),
9 years ago
0, // Don't interrupt or he'll end up running around with a needle all the time
0,
"Heal"
},
};
8 years ago
Task_t tlFaceTarget[] =
9 years ago
{
{ TASK_STOP_MOVING, 0.0f },
{ TASK_FACE_TARGET, 0.0f },
8 years ago
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE },
9 years ago
};
8 years ago
Schedule_t slFaceTarget[] =
9 years ago
{
{
tlFaceTarget,
8 years ago
ARRAYSIZE( tlFaceTarget ),
9 years ago
bits_COND_CLIENT_PUSH |
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND,
bits_SOUND_COMBAT |
bits_SOUND_DANGER,
"FaceTarget"
},
};
8 years ago
Task_t tlSciPanic[] =
9 years ago
{
{ TASK_STOP_MOVING, 0.0f },
{ TASK_FACE_ENEMY, 0.0f },
{ TASK_SCREAM, 0.0f },
8 years ago
{ TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_EXCITED }, // This is really fear-stricken excitement
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
9 years ago
};
8 years ago
Schedule_t slSciPanic[] =
9 years ago
{
{
tlSciPanic,
8 years ago
ARRAYSIZE( tlSciPanic ),
9 years ago
0,
0,
"SciPanic"
},
};
8 years ago
Task_t tlIdleSciStand[] =
9 years ago
{
8 years ago
{ TASK_STOP_MOVING, 0 },
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
{ TASK_WAIT, 2.0f }, // repick IDLESTAND every two seconds.
8 years ago
{ TASK_TLK_HEADRESET, (float)0 }, // reset head position
9 years ago
};
8 years ago
Schedule_t slIdleSciStand[] =
9 years ago
{
8 years ago
{
9 years ago
tlIdleSciStand,
8 years ago
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 |
9 years ago
bits_COND_PROVOKED,
8 years ago
bits_SOUND_COMBAT |// sound flags
//bits_SOUND_PLAYER |
//bits_SOUND_WORLD |
bits_SOUND_DANGER |
bits_SOUND_MEAT |// scents
bits_SOUND_CARCASS |
9 years ago
bits_SOUND_GARBAGE,
"IdleSciStand"
},
};
8 years ago
Task_t tlScientistCover[] =
9 years ago
{
8 years ago
{ 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 },
8 years ago
{ TASK_SET_SCHEDULE, (float)SCHED_HIDE },
9 years ago
};
8 years ago
Schedule_t slScientistCover[] =
9 years ago
{
8 years ago
{
9 years ago
tlScientistCover,
8 years ago
ARRAYSIZE( tlScientistCover ),
9 years ago
bits_COND_NEW_ENEMY,
0,
"ScientistCover"
},
};
8 years ago
Task_t tlScientistHide[] =
9 years ago
{
8 years ago
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic!
{ TASK_STOP_MOVING, 0.0f },
8 years ago
{ TASK_PLAY_SEQUENCE, (float)ACT_CROUCH },
{ TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, // FIXME: This looks lame
{ TASK_WAIT_RANDOM, 10.0f },
9 years ago
};
8 years ago
Schedule_t slScientistHide[] =
9 years ago
{
8 years ago
{
9 years ago
tlScientistHide,
8 years ago
ARRAYSIZE( tlScientistHide ),
9 years ago
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"
},
};
8 years ago
Task_t tlScientistStartle[] =
9 years ago
{
8 years ago
{ 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 },
8 years ago
{ TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH },
{ TASK_RANDOM_SCREAM, 0.1f }, // Scream again 10% of the time
8 years ago
{ TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCHIDLE },
{ TASK_WAIT_RANDOM, 1.0f },
9 years ago
};
8 years ago
Schedule_t slScientistStartle[] =
9 years ago
{
8 years ago
{
9 years ago
tlScientistStartle,
8 years ago
ARRAYSIZE( tlScientistStartle ),
9 years ago
bits_COND_NEW_ENEMY |
bits_COND_SEE_ENEMY |
bits_COND_SEE_HATE |
bits_COND_SEE_FEAR |
bits_COND_SEE_DISLIKE,
0,
"ScientistStartle"
},
};
8 years ago
Task_t tlFear[] =
9 years ago
{
{ TASK_STOP_MOVING, 0.0f },
{ TASK_FACE_ENEMY, 0.0f },
{ TASK_SAY_FEAR, 0.0f },
8 years ago
//{ TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY },
9 years ago
};
8 years ago
Schedule_t slFear[] =
9 years ago
{
8 years ago
{
9 years ago
tlFear,
8 years ago
ARRAYSIZE( tlFear ),
9 years ago
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 )
9 years ago
void CScientist::DeclineFollowing( void )
{
Talk( 10 );
m_hTalkTarget = m_hEnemy;
PlaySentence( "SC_POK", 2, VOL_NORM, ATTN_NORM );
}
8 years ago
void CScientist::Scream( void )
9 years ago
{
8 years ago
if( FOkToSpeak() )
9 years ago
{
Talk( 10 );
m_hTalkTarget = m_hEnemy;
PlaySentence( "SC_SCREAM", RANDOM_FLOAT( 3.0f, 6.0f ), VOL_NORM, ATTN_NORM );
9 years ago
}
}
Activity CScientist::GetStoppedActivity( void )
{
if( m_hEnemy != 0 )
9 years ago
return ACT_EXCITED;
return CTalkMonster::GetStoppedActivity();
}
8 years ago
void CScientist::StartTask( Task_t *pTask )
9 years ago
{
switch( pTask->iTask )
{
case TASK_SAY_HEAL:
8 years ago
//if( FOkToSpeak() )
9 years ago
Talk( 2 );
m_hTalkTarget = m_hTargetEnt;
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 )
9 years ago
Scream();
TaskComplete();
break;
case TASK_SAY_FEAR:
8 years ago
if( FOkToSpeak() )
9 years ago
{
Talk( 2 );
m_hTalkTarget = m_hEnemy;
//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() )
9 years ago
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 )
{
9 years ago
TaskComplete();
}
9 years ago
else
{
m_vecMoveGoal = m_hTargetEnt->pev->origin;
8 years ago
if( !MoveToTarget( ACT_WALK_SCARED, 0.5 ) )
9 years ago
TaskFail();
}
}
break;
default:
CTalkMonster::StartTask( pTask );
break;
}
}
8 years ago
void CScientist::RunTask( Task_t *pTask )
9 years ago
{
8 years ago
switch( pTask->iTask )
9 years ago
{
case TASK_RUN_PATH_SCARED:
8 years ago
if( MovementIsComplete() )
9 years ago
TaskComplete();
8 years ago
if( RANDOM_LONG( 0, 31 ) < 8 )
9 years ago
Scream();
break;
case TASK_MOVE_TO_TARGET_RANGE_SCARED:
{
8 years ago
if( RANDOM_LONG( 0, 63 ) < 8 )
9 years ago
Scream();
if( m_hEnemy == 0 )
9 years ago
{
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 )
9 years ago
{
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
8 years ago
if( distance < pTask->flData )
9 years ago
{
TaskComplete();
RouteClear(); // Stop moving
}
8 years ago
else if( distance < 190 && m_movementActivity != ACT_WALK_SCARED )
9 years ago
m_movementActivity = ACT_WALK_SCARED;
8 years ago
else if( distance >= 270 && m_movementActivity != ACT_RUN_SCARED )
9 years ago
m_movementActivity = ACT_RUN_SCARED;
}
}
break;
case TASK_HEAL:
8 years ago
if( m_fSequenceFinished )
9 years ago
{
TaskComplete();
}
else
{
8 years ago
if( TargetDistance() > 90 )
9 years ago
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.
//=========================================================
8 years ago
int CScientist::Classify( void )
9 years ago
{
8 years ago
return CLASS_HUMAN_PASSIVE;
9 years ago
}
//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
8 years ago
void CScientist::SetYawSpeed( void )
9 years ago
{
int ys;
ys = 90;
8 years ago
switch( m_Activity )
9 years ago
{
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;
9 years ago
}
pev->yaw_speed = ys;
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
8 years ago
void CScientist::HandleAnimEvent( MonsterEvent_t *pEvent )
9 years ago
{
switch( pEvent->event )
{
case SCIENTIST_AE_HEAL: // Heal my target (if within range)
Heal();
break;
case SCIENTIST_AE_NEEDLEON:
{
8 years ago
int oldBody = pev->body;
pev->body = ( oldBody % NUM_SCIENTIST_HEADS_OPFOR ) + NUM_SCIENTIST_HEADS_OPFOR * 1;
9 years ago
}
break;
case SCIENTIST_AE_NEEDLEOFF:
{
8 years ago
int oldBody = pev->body;
pev->body = ( oldBody % NUM_SCIENTIST_HEADS_OPFOR ) + NUM_SCIENTIST_HEADS_OPFOR * 0;
9 years ago
}
break;
default:
CTalkMonster::HandleAnimEvent( pEvent );
}
}
//=========================================================
// Spawn
//=========================================================
8 years ago
void CScientist::Spawn( void )
9 years ago
{
// We need to set it before precache so the right voice will be chosen
if( pev->body == -1 )
{
// -1 chooses a random head
pev->body = RANDOM_LONG( 0, NUM_SCIENTIST_HEADS - 1 );// pick a head, any head
}
8 years ago
Precache();
9 years ago
8 years ago
SET_MODEL( ENT( pev ), "models/scientist.mdl" );
UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX );
9 years ago
8 years ago
pev->solid = SOLID_SLIDEBOX;
pev->movetype = MOVETYPE_STEP;
m_bloodColor = BLOOD_COLOR_RED;
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;
9 years ago
8 years ago
//m_flDistTooFar = 256.0;
9 years ago
8 years ago
m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE;
9 years ago
// White hands
pev->skin = 0;
// Luther is black, make his hands black
8 years ago
if( pev->body == HEAD_LUTHER )
9 years ago
pev->skin = 1;
9 years ago
MonsterInit();
if (!m_fStartSuspicious)
SetUse( &CTalkMonster::FollowerUse );
9 years ago
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
8 years ago
void CScientist::Precache( void )
9 years ago
{
8 years ago
PRECACHE_MODEL( "models/scientist.mdl" );
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" );
9 years ago
// 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
8 years ago
void CScientist::TalkInit()
9 years ago
{
CTalkMonster::TalkInit();
// scientists speach group names (group names are in sentences.txt)
8 years ago
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";
9 years ago
m_szGrp[TLK_PQUESTION] = "SC_PQUEST";
8 years ago
m_szGrp[TLK_SMELL] = "SC_SMELL";
8 years ago
m_szGrp[TLK_WOUND] = "SC_WOUND";
m_szGrp[TLK_MORTAL] = "SC_MORTAL";
9 years ago
// get voice for head
switch( pev->body % NUM_SCIENTIST_HEADS_OPFOR )
9 years ago
{
default:
8 years ago
case HEAD_GLASSES:
m_voicePitch = 105;
break; //glasses
case HEAD_EINSTEIN:
case HEAD_EINSTEIN_WITH_BOOK:
8 years ago
m_voicePitch = 100;
break; //einstein
case HEAD_LUTHER:
m_voicePitch = 95;
break; //luther
case HEAD_SLICK:
case HEAD_SLICK_WITH_STICK:
8 years ago
m_voicePitch = 100;
break; //slick
9 years ago
}
}
8 years ago
int CScientist::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
9 years ago
{
8 years ago
if( pevInflictor && pevInflictor->flags & FL_CLIENT )
9 years ago
{
Remember( bits_MEMORY_PROVOKED );
StopFollowing( TRUE );
}
// make sure friends talk about it if player hurts scientist...
8 years ago
return CTalkMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType );
9 years ago
}
//=========================================================
// 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.
//=========================================================
8 years ago
int CScientist::ISoundMask( void )
9 years ago
{
8 years ago
return bits_SOUND_WORLD |
bits_SOUND_COMBAT |
bits_SOUND_CARCASS |
bits_SOUND_MEAT |
bits_SOUND_GARBAGE |
8 years ago
bits_SOUND_DANGER |
9 years ago
bits_SOUND_PLAYER;
}
9 years ago
//=========================================================
// PainSound
//=========================================================
8 years ago
void CScientist::PainSound( void )
9 years ago
{
8 years ago
if( gpGlobals->time < m_painTime )
9 years ago
return;
m_painTime = gpGlobals->time + RANDOM_FLOAT( 0.5f, 0.75f );
9 years ago
8 years ago
switch( RANDOM_LONG( 0, 4 ) )
9 years ago
{
8 years ago
case 0:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch() );
break;
case 1:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "scientist/sci_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch() );
break;
case 2:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "scientist/sci_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch() );
break;
case 3:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, GetVoicePitch() );
break;
case 4:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, GetVoicePitch() );
break;
9 years ago
}
}
//=========================================================
// DeathSound
//=========================================================
8 years ago
void CScientist::DeathSound( void )
9 years ago
{
PainSound();
}
void CScientist::Killed( entvars_t *pevAttacker, int iGib )
{
SetUse( NULL );
CTalkMonster::Killed( pevAttacker, iGib );
}
8 years ago
void CScientist::SetActivity( Activity newActivity )
9 years ago
{
8 years ago
int iSequence;
9 years ago
8 years ago
iSequence = LookupActivity( newActivity );
9 years ago
// Set to the desired anim, or default anim if the desired is not present
8 years ago
if( iSequence == ACTIVITY_NOT_AVAILABLE )
9 years ago
newActivity = ACT_IDLE;
CTalkMonster::SetActivity( newActivity );
}
8 years ago
Schedule_t *CScientist::GetScheduleOfType( int Type )
9 years ago
{
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
8 years ago
// when 'used'
psched = CTalkMonster::GetScheduleOfType( Type );
9 years ago
8 years ago
if( psched == slIdleStand )
9 years ago
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
8 years ago
psched = CTalkMonster::GetScheduleOfType( Type );
9 years ago
8 years ago
if( psched == slIdleStand )
9 years ago
return slIdleSciStand;
else
return psched;
case SCHED_HIDE:
return slScientistHide;
case SCHED_STARTLE:
return slScientistStartle;
case SCHED_FEAR:
return slFear;
}
return CTalkMonster::GetScheduleOfType( Type );
}
8 years ago
Schedule_t *CScientist::GetSchedule( void )
9 years ago
{
// so we don't keep calling through the EHANDLE stuff
CBaseEntity *pEnemy = m_hEnemy;
8 years ago
if( HasConditions( bits_COND_HEAR_SOUND ) )
9 years ago
{
CSound *pSound;
pSound = PBestSound();
ASSERT( pSound != NULL );
8 years ago
if( pSound && ( pSound->m_iType & bits_SOUND_DANGER ) )
9 years ago
return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND );
}
switch( m_MonsterState )
{
case MONSTERSTATE_ALERT:
9 years ago
case MONSTERSTATE_IDLE:
8 years ago
if( pEnemy )
9 years ago
{
8 years ago
if( HasConditions( bits_COND_SEE_ENEMY ) )
9 years ago
m_fearTime = gpGlobals->time;
8 years ago
else if( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert
9 years ago
{
m_hEnemy = 0;
pEnemy = 0;
9 years ago
}
}
8 years ago
if( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) )
9 years ago
{
// flinch if hurt
return GetScheduleOfType( SCHED_SMALL_FLINCH );
}
// Cower when you hear something scary
8 years ago
if( HasConditions( bits_COND_HEAR_SOUND ) )
9 years ago
{
CSound *pSound;
pSound = PBestSound();
ASSERT( pSound != NULL );
8 years ago
if( pSound )
9 years ago
{
8 years ago
if( pSound->m_iType & ( bits_SOUND_DANGER | bits_SOUND_COMBAT ) )
9 years ago
{
8 years ago
if( gpGlobals->time - m_fearTime > 3 ) // Only cower every 3 seconds or so
9 years ago
{
m_fearTime = gpGlobals->time; // Update last fear
return GetScheduleOfType( SCHED_STARTLE ); // This will just duck for a second
}
}
}
}
// Behavior for following the player
8 years ago
if( IsFollowing() )
9 years ago
{
8 years ago
if( !m_hTargetEnt->IsAlive() )
9 years ago
{
// UNDONE: Comment about the recently dead player here?
StopFollowing( FALSE );
break;
}
int relationship = R_NO;
// Nothing scary, just me and the player
8 years ago
if( pEnemy != NULL )
9 years ago
relationship = IRelationship( pEnemy );
// UNDONE: Model fear properly, fix R_FR and add multiple levels of fear
8 years ago
if( relationship != R_DL && relationship != R_HT )
9 years ago
{
// If I'm already close enough to my target
8 years ago
if( TargetDistance() <= 128 )
9 years ago
{
8 years ago
if( CanHeal() ) // Heal opportunistically
9 years ago
return slHeal;
8 years ago
if( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move
9 years ago
return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW );
}
return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow.
}
else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared
{
8 years ago
if( HasConditions( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react
9 years ago
return GetScheduleOfType( SCHED_FEAR ); // React to something scary
return GetScheduleOfType( SCHED_TARGET_FACE_SCARED ); // face and follow, but I'm scared!
}
}
8 years ago
if( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move
9 years ago
return GetScheduleOfType( SCHED_MOVE_AWAY );
// try to say something about smells
TrySmellTalk();
break;
case MONSTERSTATE_COMBAT:
8 years ago
if( HasConditions( bits_COND_NEW_ENEMY ) )
9 years ago
return slFear; // Point and scream!
8 years ago
if( HasConditions( bits_COND_SEE_ENEMY ) )
9 years ago
return slScientistCover; // Take Cover
8 years ago
if( HasConditions( bits_COND_HEAR_SOUND ) )
9 years ago
return slTakeCoverFromBestSound; // Cower and panic from the scary sound!
return slScientistCover; // Run & Cower
break;
default:
break;
9 years ago
}
return CTalkMonster::GetSchedule();
}
8 years ago
MONSTERSTATE CScientist::GetIdealState( void )
9 years ago
{
8 years ago
switch( m_MonsterState )
9 years ago
{
case MONSTERSTATE_ALERT:
case MONSTERSTATE_IDLE:
8 years ago
if( HasConditions( bits_COND_NEW_ENEMY ) )
9 years ago
{
8 years ago
if( IsFollowing() )
9 years ago
{
int relationship = IRelationship( m_hEnemy );
8 years ago
if( relationship != R_FR || ( relationship != R_HT && !HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) )
9 years ago
{
// Don't go to combat if you're following the player
m_IdealMonsterState = MONSTERSTATE_ALERT;
return m_IdealMonsterState;
}
StopFollowing( TRUE );
}
}
8 years ago
else if( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) )
9 years ago
{
// Stop following if you take damage
8 years ago
if( IsFollowing() )
9 years ago
StopFollowing( TRUE );
}
break;
case MONSTERSTATE_COMBAT:
{
CBaseEntity *pEnemy = m_hEnemy;
8 years ago
if( pEnemy != NULL )
9 years ago
{
8 years ago
if( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert
9 years ago
{
// Strip enemy when going to alert
m_IdealMonsterState = MONSTERSTATE_ALERT;
m_hEnemy = 0;
9 years ago
return m_IdealMonsterState;
}
9 years ago
// Follow if only scared a little
if( m_hTargetEnt != 0 )
9 years ago
{
m_IdealMonsterState = MONSTERSTATE_ALERT;
return m_IdealMonsterState;
}
8 years ago
if( HasConditions( bits_COND_SEE_ENEMY ) )
9 years ago
{
m_fearTime = gpGlobals->time;
m_IdealMonsterState = MONSTERSTATE_COMBAT;
return m_IdealMonsterState;
}
}
}
break;
default:
break;
9 years ago
}
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 ) ) )
9 years ago
return FALSE;
return TRUE;
}
void CScientist::Heal( void )
{
8 years ago
if( !CanHeal() )
9 years ago
return;
Vector target = m_hTargetEnt->pev->origin - pev->origin;
if( target.Length() > 100.0f )
9 years ago
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 )
{
5 years ago
static int array[6] = { 1, 4, 2, 5, 0, 3 };
if( arrayNumber < 6 )
8 years ago
return array[arrayNumber];
9 years ago
return arrayNumber;
}
//=========================================================
// Dead Scientist PROP
//=========================================================
const char *CDeadScientist::m_szPoses[] =
8 years ago
{
"lying_on_back",
"lying_on_stomach",
"dead_sitting",
"dead_hang",
"dead_table1",
"dead_table2",
"dead_table3"
};
9 years ago
void CDeadScientist::KeyValue( KeyValueData *pkvd )
{
8 years ago
if( FStrEq( pkvd->szKeyName, "pose" ) )
9 years ago
{
8 years ago
m_iPose = atoi( pkvd->szValue );
9 years ago
pkvd->fHandled = TRUE;
}
else
CBaseMonster::KeyValue( pkvd );
}
LINK_ENTITY_TO_CLASS( monster_scientist_dead, CDeadScientist )
9 years ago
//
// ********** DeadScientist SPAWN **********
//
8 years ago
void CDeadScientist::Spawn()
9 years ago
{
8 years ago
PRECACHE_MODEL( "models/scientist.mdl" );
SET_MODEL( ENT( pev ), "models/scientist.mdl" );
pev->effects = 0;
pev->sequence = 0;
9 years ago
// Corpses have less health
8 years ago
pev->health = 8;//gSkillData.scientistHealth;
9 years ago
m_bloodColor = BLOOD_COLOR_RED;
8 years ago
if( pev->body == -1 )
{
// -1 chooses a random head
8 years ago
pev->body = RANDOM_LONG( 0, NUM_SCIENTIST_HEADS - 1 );// pick a head, any head
9 years ago
}
9 years ago
// Luther is black, make his hands black
8 years ago
if( pev->body == HEAD_LUTHER )
9 years ago
pev->skin = 1;
else
pev->skin = 0;
pev->sequence = LookupSequence( m_szPoses[m_iPose] );
8 years ago
if( pev->sequence == -1 )
9 years ago
{
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
//=========================================================
LINK_ENTITY_TO_CLASS( monster_sitting_scientist, CSittingScientist )
8 years ago
TYPEDESCRIPTION CSittingScientist::m_SaveData[] =
9 years ago
{
// 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 )
9 years ago
// animation sequence aliases
typedef enum
{
SITTING_ANIM_sitlookleft,
SITTING_ANIM_sitlookright,
SITTING_ANIM_sitscared,
SITTING_ANIM_sitting2,
SITTING_ANIM_sitting3
} SITTING_ANIM;
//
// ********** Scientist SPAWN **********
//
8 years ago
void CSittingScientist::Spawn()
9 years ago
{
8 years ago
PRECACHE_MODEL( "models/scientist.mdl" );
SET_MODEL( ENT( pev ), "models/scientist.mdl" );
9 years ago
Precache();
InitBoneControllers();
8 years ago
UTIL_SetSize( pev, Vector( -14, -14, 0 ), Vector( 14, 14, 36 ) );
9 years ago
8 years ago
pev->solid = SOLID_SLIDEBOX;
pev->movetype = MOVETYPE_STEP;
pev->effects = 0;
pev->health = 50;
9 years ago
m_bloodColor = BLOOD_COLOR_RED;
8 years ago
m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result )
9 years ago
8 years ago
m_afCapability= bits_CAP_HEAR | bits_CAP_TURN_HEAD;
9 years ago
8 years ago
SetBits( pev->spawnflags, SF_MONSTER_PREDISASTER ); // predisaster only!
9 years ago
8 years ago
if( pev->body == -1 )
{
// -1 chooses a random head
8 years ago
pev->body = RANDOM_LONG( 0, NUM_SCIENTIST_HEADS - 1 );// pick a head, any head
9 years ago
}
9 years ago
// Luther is black, make his hands black
8 years ago
if( pev->body == HEAD_LUTHER )
9 years ago
pev->skin = 1;
8 years ago
9 years ago
m_baseSequence = LookupSequence( "sitlookleft" );
8 years ago
pev->sequence = m_baseSequence + RANDOM_LONG( 0, 4 );
ResetSequenceInfo();
SetThink( &CSittingScientist::SittingThink );
pev->nextthink = gpGlobals->time + 0.1f;
9 years ago
8 years ago
DROP_TO_FLOOR( ENT( pev ) );
9 years ago
}
8 years ago
void CSittingScientist::Precache( void )
9 years ago
{
m_baseSequence = LookupSequence( "sitlookleft" );
TalkInit();
}
//=========================================================
// ID as a passive human
//=========================================================
8 years ago
int CSittingScientist::Classify( void )
9 years ago
{
8 years ago
return CLASS_HUMAN_PASSIVE;
9 years ago
}
int CSittingScientist::FriendNumber( int arrayNumber )
{
5 years ago
static int array[6] = { 2, 5, 1, 4, 0, 3 };
if( arrayNumber < 6 )
8 years ago
return array[arrayNumber];
9 years ago
return arrayNumber;
}
//=========================================================
// sit, do stuff
//=========================================================
8 years ago
void CSittingScientist::SittingThink( void )
9 years ago
{
8 years ago
CBaseEntity *pent;
9 years ago
8 years ago
StudioFrameAdvance();
9 years ago
// try to greet player
8 years ago
if( FIdleHello() )
9 years ago
{
8 years ago
pent = FindNearestFriend( TRUE );
if( pent )
9 years ago
{
8 years ago
float yaw = VecToYaw( pent->pev->origin - pev->origin ) - pev->angles.y;
9 years ago
8 years ago
if( yaw > 180 )
yaw -= 360;
if( yaw < -180 )
yaw += 360;
if( yaw > 0 )
9 years ago
pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft;
else
pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright;
8 years ago
ResetSequenceInfo();
9 years ago
pev->frame = 0;
SetBoneController( 0, 0 );
}
}
8 years ago
else if( m_fSequenceFinished )
9 years ago
{
8 years ago
int i = RANDOM_LONG( 0, 99 );
9 years ago
m_headTurn = 0;
8 years ago
if( m_flResponseDelay && gpGlobals->time > m_flResponseDelay )
9 years ago
{
// respond to question
IdleRespond();
pev->sequence = m_baseSequence + SITTING_ANIM_sitscared;
m_flResponseDelay = 0;
}
8 years ago
else if( i < 30 )
9 years ago
{
pev->sequence = m_baseSequence + SITTING_ANIM_sitting3;
// turn towards player or nearest friend and speak
8 years ago
if( !FBitSet( m_bitsSaid, bit_saidHelloPlayer ) )
pent = FindNearestFriend( TRUE );
9 years ago
else
8 years ago
pent = FindNearestFriend( FALSE );
9 years ago
8 years ago
if( !FIdleSpeak() || !pent )
{
m_headTurn = RANDOM_LONG( 0, 8 ) * 10 - 40;
9 years ago
pev->sequence = m_baseSequence + SITTING_ANIM_sitting3;
}
else
{
// only turn head if we spoke
8 years ago
float yaw = VecToYaw( pent->pev->origin - pev->origin ) - pev->angles.y;
9 years ago
8 years ago
if( yaw > 180 )
yaw -= 360;
if( yaw < -180 )
yaw += 360;
if( yaw > 0 )
9 years ago
pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft;
else
pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright;
8 years ago
//ALERT( at_console, "sitting speak\n" );
9 years ago
}
}
8 years ago
else if( i < 60 )
9 years ago
{
pev->sequence = m_baseSequence + SITTING_ANIM_sitting3;
8 years ago
m_headTurn = RANDOM_LONG( 0, 8 ) * 10 - 40;
if( RANDOM_LONG( 0, 99 ) < 5 )
9 years ago
{
8 years ago
//ALERT( at_console, "sitting speak2\n" );
9 years ago
FIdleSpeak();
}
}
8 years ago
else if( i < 80 )
9 years ago
{
pev->sequence = m_baseSequence + SITTING_ANIM_sitting2;
}
8 years ago
else if( i < 100 )
9 years ago
{
pev->sequence = m_baseSequence + SITTING_ANIM_sitscared;
}
ResetSequenceInfo( );
pev->frame = 0;
SetBoneController( 0, m_headTurn );
}
pev->nextthink = gpGlobals->time + 0.1f;
9 years ago
}
// prepare sitting scientist to answer a question
8 years ago
void CSittingScientist::SetAnswerQuestion( CTalkMonster *pSpeaker )
9 years ago
{
m_flResponseDelay = gpGlobals->time + RANDOM_FLOAT( 3.0f, 4.0f );
9 years ago
m_hTalkTarget = (CBaseMonster *)pSpeaker;
}
//=========================================================
// FIdleSpeak
// ask question of nearby friend, or make statement
//=========================================================
8 years ago
int CSittingScientist::FIdleSpeak( void )
9 years ago
{
// try to start a conversation, or make statement
int pitch;
8 years ago
if( !FOkToSpeak() )
9 years ago
return FALSE;
// set global min delay for next conversation
8 years ago
CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT( 4.8, 5.2 );
9 years ago
pitch = GetVoicePitch();
8 years ago
9 years ago
// 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
8 years ago
CBaseEntity *pentFriend = FindNearestFriend( FALSE );
9 years ago
8 years ago
if( pentFriend && RANDOM_LONG( 0, 1 ) )
9 years ago
{
8 years ago
CTalkMonster *pTalkMonster = GetClassPtr( (CTalkMonster *)pentFriend->pev );
9 years ago
pTalkMonster->SetAnswerQuestion( this );
8 years ago
IdleHeadTurn( pentFriend->pev->origin );
SENTENCEG_PlayRndSz( ENT( pev ), m_szGrp[TLK_PQUESTION], 1.0, ATTN_IDLE, 0, pitch );
9 years ago
// set global min delay for next conversation
8 years ago
CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT( 4.8, 5.2 );
9 years ago
return TRUE;
}
// otherwise, play an idle statement
8 years ago
if( RANDOM_LONG( 0, 1 ) )
9 years ago
{
8 years ago
SENTENCEG_PlayRndSz( ENT( pev ), m_szGrp[TLK_PIDLE], 1.0, ATTN_IDLE, 0, pitch );
9 years ago
// set global min delay for next conversation
8 years ago
CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT( 4.8, 5.2 );
9 years ago
return TRUE;
}
// never spoke
CTalkMonster::g_talkWaitTime = 0;
return FALSE;
}