/***
*
* Original code by VALVe
* Edit by The Unbelievable Systems
*
****/
//=========================================================
// wscientist (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"


#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model
enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 };

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
//=======================================================

class CScientistWoman : public CTalkMonster
{
public:
void Spawn( void );
void Precache( void );

void SetYawSpeed( void );
int Classify ( void );
void HandleAnimEvent( MonsterEvent_t *pEvent );
void RunTask( Task_t *pTask );
void StartTask( Task_t *pTask );
int ObjectCaps( void ) { return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE; }
int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType);
virtual int FriendNumber( int arrayNumber );
void SetActivity ( Activity newActivity );
Activity GetStoppedActivity( void );
int ISoundMask( void );
void DeclineFollowing( void );

float CoverRadius( void ) { return 1200; } // Need more room for cover because scientists want to get far away!
BOOL DisregardEnemy( CBaseEntity *pEnemy ) { return !pEnemy->IsAlive() || (gpGlobals->time - m_fearTime) > 15; }

BOOL CanHeal( void );
void Heal( void );
void Scream( void );

// Override these to set behavior
Schedule_t *GetScheduleOfType ( int Type );
Schedule_t *GetSchedule ( void );
MONSTERSTATE GetIdealState ( void );

void DeathSound( void );
void PainSound( void );

void TalkInit( void );

void Killed( entvars_t *pevAttacker, int iGib );

virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];

CUSTOM_SCHEDULES;

private:
float m_painTime;
float m_healTime;
float m_fearTime;
};

LINK_ENTITY_TO_CLASS( monster_wscientist, CScientistWoman );

TYPEDESCRIPTION CScientistWoman::m_SaveData[] =
{
DEFINE_FIELD( CScientistWoman, m_painTime, FIELD_TIME ),
DEFINE_FIELD( CScientistWoman, m_healTime, FIELD_TIME ),
DEFINE_FIELD( CScientistWoman, m_fearTime, FIELD_TIME ),
};

IMPLEMENT_SAVERESTORE( CScientistWoman, CTalkMonster );

//=========================================================
// AI Schedules Specific to this monster
//=========================================================
Task_t tlWFollow[] =
{
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_CANT_FOLLOW }, // If you fail, bail out of follow
{ TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client)
// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE },
};

Schedule_t slWFollow[] =
{
{
tlWFollow,
HL_ARRAYSIZE ( tlWFollow ),
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 tlWFollowScared[] =
{
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE },// If you fail, follow normally
{ TASK_MOVE_TO_TARGET_RANGE_SCARED,(float)128 }, // Move within 128 of target ent (client)
// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED },
};

Schedule_t slWFollowScared[] =
{
{
tlWFollowScared,
HL_ARRAYSIZE ( tlWFollowScared ),
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND |
bits_COND_LIGHT_DAMAGE |
bits_COND_HEAVY_DAMAGE,
bits_SOUND_DANGER,
"FollowScared"
},
};

Task_t tlWFaceTargetScared[] =
{
{ TASK_FACE_TARGET, (float)0 },
{ TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE },
{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED },
};

Schedule_t slWFaceTargetScared[] =
{
{
tlWFaceTargetScared,
HL_ARRAYSIZE ( tlWFaceTargetScared ),
bits_COND_HEAR_SOUND |
bits_COND_NEW_ENEMY,
bits_SOUND_DANGER,
"FaceTargetScared"
},
};

Task_t tlWStopFollowing[] =
{
{ TASK_CANT_FOLLOW, (float)0 },
};

Schedule_t slWStopFollowing[] =
{
{
tlWStopFollowing,
HL_ARRAYSIZE ( tlWStopFollowing ),
0,
0,
"StopFollowing"
},
};


Task_t tlWHeal[] =
{
{ TASK_MOVE_TO_TARGET_RANGE,(float)50 }, // 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, (float)0 },
{ TASK_SAY_HEAL, (float)0 },
{ TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM }, // Whip out the needle
{ TASK_HEAL, (float)0 }, // Put it in the player
{ TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM }, // Put away the needle
};

Schedule_t slWHeal[] =
{
{
tlWHeal,
HL_ARRAYSIZE ( tlWHeal ),
0, // Don't interrupt or he'll end up running around with a needle all the time
0,
"Heal"
},
};


Task_t tlWFaceTarget[] =
{
{ TASK_STOP_MOVING, (float)0 },
{ TASK_FACE_TARGET, (float)0 },
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE },
};

Schedule_t slWFaceTarget[] =
{
{
tlWFaceTarget,
HL_ARRAYSIZE ( tlWFaceTarget ),
bits_COND_CLIENT_PUSH |
bits_COND_NEW_ENEMY |
bits_COND_HEAR_SOUND,
bits_SOUND_COMBAT |
bits_SOUND_DANGER,
"FaceTarget"
},
};


Task_t tlWSciPanic[] =
{
{ TASK_STOP_MOVING, (float)0 },
{ TASK_FACE_ENEMY, (float)0 },
{ TASK_SCREAM, (float)0 },
{ TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_EXCITED }, // This is really fear-stricken excitement
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
};

Schedule_t slWSciPanic[] =
{
{
tlWSciPanic,
HL_ARRAYSIZE ( tlWSciPanic ),
0,
0,
"SciPanic"
},
};


Task_t tlWIdleSciStand[] =
{
{ TASK_STOP_MOVING, 0 },
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
{ TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds.
{ TASK_TLK_HEADRESET, (float)0 }, // reset head position
};

Schedule_t slWIdleSciStand[] =
{
{
tlWIdleSciStand,
HL_ARRAYSIZE ( tlWIdleSciStand ),
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 tlWScientistCover[] =
{
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic!
{ TASK_STOP_MOVING, (float)0 },
{ TASK_FIND_COVER_FROM_ENEMY, (float)0 },
{ TASK_RUN_PATH_SCARED, (float)0 },
{ TASK_TURN_LEFT, (float)179 },
{ TASK_SET_SCHEDULE, (float)SCHED_HIDE },
};

Schedule_t slWScientistCover[] =
{
{
tlWScientistCover,
HL_ARRAYSIZE ( tlWScientistCover ),
bits_COND_NEW_ENEMY,
0,
"ScientistCover"
},
};



Task_t tlWScientistHide[] =
{
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic!
{ TASK_STOP_MOVING, (float)0 },
{ TASK_PLAY_SEQUENCE, (float)ACT_CROUCH },
{ TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, // FIXME: This looks lame
{ TASK_WAIT_RANDOM, (float)10.0 },
};

Schedule_t slWScientistHide[] =
{
{
tlWScientistHide,
HL_ARRAYSIZE ( tlWScientistHide ),
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 tlWScientistStartle[] =
{
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic!
{ TASK_RANDOM_SCREAM, (float)0.3 }, // Scream 30% of the time
{ TASK_STOP_MOVING, (float)0 },
{ TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH },
{ TASK_RANDOM_SCREAM, (float)0.1 }, // Scream again 10% of the time
{ TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCHIDLE },
{ TASK_WAIT_RANDOM, (float)1.0 },
};

Schedule_t slWScientistStartle[] =
{
{
tlWScientistStartle,
HL_ARRAYSIZE ( tlWScientistStartle ),
bits_COND_NEW_ENEMY |
bits_COND_SEE_ENEMY |
bits_COND_SEE_HATE |
bits_COND_SEE_FEAR |
bits_COND_SEE_DISLIKE,
0,
"ScientistStartle"
},
};



Task_t tlWFear[] =
{
{ TASK_STOP_MOVING, (float)0 },
{ TASK_FACE_ENEMY, (float)0 },
{ TASK_SAY_FEAR, (float)0 },
// { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY },
};

Schedule_t slWFear[] =
{
{
tlWFear,
HL_ARRAYSIZE ( tlWFear ),
bits_COND_NEW_ENEMY,
0,
"Fear"
},
};


DEFINE_CUSTOM_SCHEDULES( CScientistWoman )
{
slWFollow,
slWFaceTarget,
slWIdleSciStand,
slWFear,
slWScientistCover,
slWScientistHide,
slWScientistStartle,
slWHeal,
slWStopFollowing,
slWSciPanic,
slWFollowScared,
slWFaceTargetScared,
};


IMPLEMENT_CUSTOM_SCHEDULES( CScientistWoman, CTalkMonster );


void CScientistWoman::DeclineFollowing( void )
{
Talk( 10 );
m_hTalkTarget = m_hEnemy;
PlaySentence( "WSC_POK", 2, VOL_NORM, ATTN_NORM );
}


void CScientistWoman :: Scream( void )
{
if ( FOkToSpeak() )
{
Talk( 10 );
m_hTalkTarget = m_hEnemy;
PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM );
}
}


Activity CScientistWoman::GetStoppedActivity( void )
{
if ( m_hEnemy != NULL )
return ACT_EXCITED;
return CTalkMonster::GetStoppedActivity();
}


void CScientistWoman :: StartTask( Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_SAY_HEAL:
// if ( FOkToSpeak() )
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, 1 ) < pTask->flData )
Scream();
TaskComplete();
break;

case TASK_SAY_FEAR:
if ( FOkToSpeak() )
{
Talk( 2 );
m_hTalkTarget = m_hEnemy;
if ( m_hEnemy->IsPlayer() )
PlaySentence( "WSC_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 )
TaskComplete();
else
{
m_vecMoveGoal = m_hTargetEnt->pev->origin;
if ( !MoveToTarget( ACT_WALK_SCARED, 0.5 ) )
TaskFail();
}
}
break;

default:
CTalkMonster::StartTask( pTask );
break;
}
}

void CScientistWoman :: 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 == NULL )
{
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.5 )
{
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 CScientistWoman :: Classify ( void )
{
return CLASS_HUMAN_PASSIVE;
}


//=========================================================
// SetYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
void CScientistWoman :: 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;
}

pev->yaw_speed = ys;
}

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CScientistWoman :: 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 CScientistWoman :: Spawn( void )
{
Precache( );

SET_MODEL(ENT(pev), "models/wscientist.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;
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( &CScientistWoman :: FollowerUse );
}

//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CScientistWoman :: Precache( void )
{
PRECACHE_MODEL("models/wscientist.mdl");
PRECACHE_SOUND("scientist/wsci_pain1.wav");

// 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 CScientistWoman :: TalkInit()
{

CTalkMonster::TalkInit();

// scientist will try to talk to friends in this order:

m_szFriends[0] = "monster_wscientist";
m_szFriends[1] = "monster_sitting_wscientist";
m_szFriends[2] = "monster_barney";

// scientists speach group names (group names are in sentences.txt)

m_szGrp[TLK_ANSWER] = "WSC_ANSWER";
m_szGrp[TLK_QUESTION] = "WSC_QUESTION";
m_szGrp[TLK_IDLE] = "WSC_IDLE";
m_szGrp[TLK_STARE] = "WSC_STARE";
m_szGrp[TLK_USE] = "WSC_OK";
m_szGrp[TLK_UNUSE] = "WSC_WAIT";
m_szGrp[TLK_STOP] = "WSC_STOP";
m_szGrp[TLK_NOSHOOT] = "WSC_SCARED";
m_szGrp[TLK_HELLO] = "WSC_HELLO";

m_szGrp[TLK_PLHURT1] = "!SC_CUREA";
m_szGrp[TLK_PLHURT2] = "!SC_CUREB";
m_szGrp[TLK_PLHURT3] = "!SC_CUREC";

m_szGrp[TLK_PHELLO] = "WSC_PHELLO";
m_szGrp[TLK_PIDLE] = "WSC_PIDLE";
m_szGrp[TLK_PQUESTION] = "WSC_PQUEST";
m_szGrp[TLK_SMELL] = "WSC_SMELL";

m_szGrp[TLK_WOUND] = "WSC_WOUND";
m_szGrp[TLK_MORTAL] = "WSC_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 CScientistWoman :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType)
{

if ( pevInflictor && pevInflictor->flags & FL_CLIENT )
{
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 CScientistWoman :: ISoundMask ( void )
{
return bits_SOUND_WORLD |
bits_SOUND_COMBAT |
bits_SOUND_DANGER |
bits_SOUND_PLAYER;
}

//=========================================================
// PainSound
//=========================================================
void CScientistWoman :: PainSound ( void )
{
if (gpGlobals->time < m_painTime )
return;

m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75);

switch (RANDOM_LONG(0,4))
{
case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break;
}
}

//=========================================================
// DeathSound
//=========================================================
void CScientistWoman :: DeathSound ( void )
{
PainSound();
}


void CScientistWoman::Killed( entvars_t *pevAttacker, int iGib )
{
SetUse( NULL );
CTalkMonster::Killed( pevAttacker, iGib );
}


void CScientistWoman :: 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* CScientistWoman :: 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 slWFaceTarget; // override this for different target face behavior
else
return psched;

case SCHED_TARGET_CHASE:
return slWFollow;

case SCHED_CANT_FOLLOW:
return slWStopFollowing;

case SCHED_PANIC:
return slWSciPanic;

case SCHED_TARGET_CHASE_SCARED:
return slWFollowScared;

case SCHED_TARGET_FACE_SCARED:
return slWFaceTargetScared;

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 slWIdleSciStand;
else
return psched;

case SCHED_HIDE:
return slWScientistHide;

case SCHED_STARTLE:
return slWScientistStartle;

case SCHED_FEAR:
return slWFear;
}

return CTalkMonster::GetScheduleOfType( Type );
}

Schedule_t *CScientistWoman :: 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 = NULL;
pEnemy = NULL;
}
}

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 slWHeal;
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.
}
else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared
{
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 slWFear; // Point and scream!
if ( HasConditions( bits_COND_SEE_ENEMY ) )
return slWScientistCover; // Take Cover

if ( HasConditions( bits_COND_HEAR_SOUND ) )
return slTakeCoverFromBestSound; // Cower and panic from the scary sound!

return slWScientistCover; // Run & Cower
break;
}

return CTalkMonster::GetSchedule();
}

MONSTERSTATE CScientistWoman :: 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 = NULL;
return m_IdealMonsterState;
}
// Follow if only scared a little
if ( m_hTargetEnt != NULL )
{
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;
}

return CTalkMonster::GetIdealState();
}


BOOL CScientistWoman::CanHeal( void )
{
if ( (m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->pev->health > (m_hTargetEnt->pev->max_health * 0.5)) )
return FALSE;

return TRUE;
}

void CScientistWoman::Heal( void )
{
if ( !CanHeal() )
return;

Vector target = m_hTargetEnt->pev->origin - pev->origin;
if ( target.Length() > 100 )
return;

m_hTargetEnt->TakeHealth( gSkillData.scientistHeal, DMG_GENERIC );
// Don't heal again for 1 minute
m_healTime = gpGlobals->time + 60;
}

int CScientistWoman::FriendNumber( int arrayNumber )
{
static int array[3] = { 1, 2, 0 };
if ( arrayNumber < 3 )
return array[ arrayNumber ];
return arrayNumber;
}


//=========================================================
// Dead Scientist PROP
//=========================================================
class CDeadWScientist : public CBaseMonster
{
public:
void Spawn( void );
int Classify ( void ) { return CLASS_HUMAN_PASSIVE; }

void KeyValue( KeyValueData *pkvd );
int m_iPose;// which sequence to display
static char *m_szPoses[7];
};
char *CDeadWScientist::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" };

void CDeadWScientist::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_wscientist_dead, CDeadWScientist );

//
// ********** DeadScientist SPAWN **********
//
void CDeadWScientist :: 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 CSittingWScientist : public CScientistWoman // 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_wscientist, CSittingWScientist );
TYPEDESCRIPTION CSittingWScientist::m_SaveData[] =
{
// Don't need to save/restore m_baseSequence (recalced)
DEFINE_FIELD( CSittingWScientist, m_headTurn, FIELD_INTEGER ),
DEFINE_FIELD( CSittingWScientist, m_flResponseDelay, FIELD_FLOAT ),
};

IMPLEMENT_SAVERESTORE( CSittingWScientist, CScientistWoman );

// 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 CSittingWScientist :: 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 (&CSittingWScientist :: SittingThink);
pev->nextthink = gpGlobals->time + 0.1;

DROP_TO_FLOOR ( ENT(pev) );
}

void CSittingWScientist :: Precache( void )
{
m_baseSequence = LookupSequence( "sitlookleft" );
TalkInit();
}

//=========================================================
// ID as a passive human
//=========================================================
int CSittingWScientist :: Classify ( void )
{
return CLASS_HUMAN_PASSIVE;
}


int CSittingWScientist::FriendNumber( int arrayNumber )
{
static int array[3] = { 2, 1, 0 };
if ( arrayNumber < 3 )
return array[ arrayNumber ];
return arrayNumber;
}



//=========================================================
// sit, do stuff
//=========================================================
void CSittingWScientist :: 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.1;
}

// prepare sitting scientist to answer a question
void CSittingWScientist :: SetAnswerQuestion( CTalkMonster *pSpeaker )
{
m_flResponseDelay = gpGlobals->time + RANDOM_FLOAT(3, 4);
m_hTalkTarget = (CBaseMonster *)pSpeaker;
}


//=========================================================
// FIdleSpeak
// ask question of nearby friend, or make statement
//=========================================================
int CSittingWScientist :: 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;
}