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.
1429 lines
35 KiB
1429 lines
35 KiB
/*** |
|
* |
|
* 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. |
|
* |
|
****/ |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "monsters.h" |
|
#include "schedule.h" |
|
#include "talkmonster.h" |
|
#include "defaultai.h" |
|
#include "scripted.h" |
|
#include "soundent.h" |
|
#include "animation.h" |
|
|
|
//========================================================= |
|
// Talking monster base class |
|
// Used for scientists and barneys |
|
//========================================================= |
|
float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once |
|
|
|
// NOTE: m_voicePitch & m_szGrp should be fixed up by precache each save/restore |
|
|
|
TYPEDESCRIPTION CTalkMonster::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CTalkMonster, m_bitsSaid, FIELD_INTEGER ), |
|
DEFINE_FIELD( CTalkMonster, m_nSpeak, FIELD_INTEGER ), |
|
|
|
// Recalc'ed in Precache() |
|
//DEFINE_FIELD( CTalkMonster, m_voicePitch, FIELD_INTEGER ), |
|
//DEFINE_FIELD( CTalkMonster, m_szGrp, FIELD_??? ), |
|
DEFINE_FIELD( CTalkMonster, m_useTime, FIELD_TIME ), |
|
DEFINE_FIELD( CTalkMonster, m_iszUse, FIELD_STRING ), |
|
DEFINE_FIELD( CTalkMonster, m_iszUnUse, FIELD_STRING ), |
|
DEFINE_FIELD( CTalkMonster, m_flLastSaidSmelled, FIELD_TIME ), |
|
DEFINE_FIELD( CTalkMonster, m_flStopTalkTime, FIELD_TIME ), |
|
DEFINE_FIELD( CTalkMonster, m_hTalkTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( CTalkMonster, m_fStartSuspicious, FIELD_BOOLEAN ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CTalkMonster, CSquadMonster ) |
|
|
|
// array of friend names |
|
const char *CTalkMonster::m_szFriends[TLK_CFRIENDS] = |
|
{ |
|
"monster_barney", |
|
"monster_scientist", |
|
"monster_sitting_scientist", |
|
"monster_otis", |
|
"monster_cleansuit_scientist", |
|
"monster_sitting_cleansuit_scientist", |
|
}; |
|
|
|
//========================================================= |
|
// AI Schedules Specific to talking monsters |
|
//========================================================= |
|
Task_t tlIdleResponse[] = |
|
{ |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, // Stop and listen |
|
{ TASK_WAIT, (float)0.5 }, // Wait until sure it's me they are talking to |
|
{ TASK_TLK_EYECONTACT, (float)0 }, // Wait until speaker is done |
|
{ TASK_TLK_RESPOND, (float)0 }, // Wait and then say my response |
|
{ TASK_TLK_IDEALYAW, (float)0 }, // look at who I'm talking to |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
{ TASK_TLK_EYECONTACT, (float)0 }, // Wait until speaker is done |
|
}; |
|
|
|
Schedule_t slIdleResponse[] = |
|
{ |
|
{ |
|
tlIdleResponse, |
|
ARRAYSIZE( tlIdleResponse ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"Idle Response" |
|
}, |
|
}; |
|
|
|
Task_t tlIdleSpeak[] = |
|
{ |
|
{ TASK_TLK_SPEAK, (float)0 },// question or remark |
|
{ TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
{ TASK_TLK_EYECONTACT, (float)0 }, |
|
{ TASK_WAIT_RANDOM, (float)0.5 }, |
|
}; |
|
|
|
Schedule_t slIdleSpeak[] = |
|
{ |
|
{ |
|
tlIdleSpeak, |
|
ARRAYSIZE( tlIdleSpeak ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_CLIENT_PUSH | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"Idle Speak" |
|
}, |
|
}; |
|
|
|
Task_t tlIdleSpeakWait[] = |
|
{ |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },// Stop and talk |
|
{ TASK_TLK_SPEAK, (float)0 },// question or remark |
|
{ TASK_TLK_EYECONTACT, (float)0 },// |
|
{ TASK_WAIT, (float)2 },// wait - used when sci is in 'use' mode to keep head turned |
|
}; |
|
|
|
Schedule_t slIdleSpeakWait[] = |
|
{ |
|
{ |
|
tlIdleSpeakWait, |
|
ARRAYSIZE( tlIdleSpeakWait ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_CLIENT_PUSH | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"Idle Speak Wait" |
|
}, |
|
}; |
|
|
|
Task_t tlIdleHello[] = |
|
{ |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE },// Stop and talk |
|
{ TASK_TLK_HELLO, (float)0 },// Try to say hello to player |
|
{ TASK_TLK_EYECONTACT, (float)0 }, |
|
{ TASK_WAIT, (float)0.5 },// wait a bit |
|
{ TASK_TLK_HELLO, (float)0 },// Try to say hello to player |
|
{ TASK_TLK_EYECONTACT, (float)0 }, |
|
{ TASK_WAIT, (float)0.5 },// wait a bit |
|
{ TASK_TLK_HELLO, (float)0 },// Try to say hello to player |
|
{ TASK_TLK_EYECONTACT, (float)0 }, |
|
{ TASK_WAIT, (float)0.5 },// wait a bit |
|
{ TASK_TLK_HELLO, (float)0 },// Try to say hello to player |
|
{ TASK_TLK_EYECONTACT, (float)0 }, |
|
{ TASK_WAIT, (float)0.5 },// wait a bit |
|
}; |
|
|
|
Schedule_t slIdleHello[] = |
|
{ |
|
{ |
|
tlIdleHello, |
|
ARRAYSIZE( tlIdleHello ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_CLIENT_PUSH | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE | |
|
bits_COND_HEAR_SOUND | |
|
bits_COND_PROVOKED, |
|
bits_SOUND_COMBAT, |
|
"Idle Hello" |
|
}, |
|
}; |
|
|
|
Task_t tlIdleStopShooting[] = |
|
{ |
|
{ TASK_TLK_STOPSHOOTING, (float)0 },// tell player to stop shooting friend |
|
// { TASK_TLK_EYECONTACT, (float)0 },// look at the player |
|
}; |
|
|
|
Schedule_t slIdleStopShooting[] = |
|
{ |
|
{ |
|
tlIdleStopShooting, |
|
ARRAYSIZE( tlIdleStopShooting ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE | |
|
bits_COND_HEAR_SOUND, |
|
0, |
|
"Idle Stop Shooting" |
|
}, |
|
}; |
|
|
|
Task_t tlMoveAway[] = |
|
{ |
|
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_MOVE_AWAY_FAIL }, |
|
{ TASK_STORE_LASTPOSITION, (float)0 }, |
|
{ TASK_MOVE_AWAY_PATH, (float)100 }, |
|
{ TASK_WALK_PATH_FOR_UNITS, (float)100 }, |
|
{ TASK_STOP_MOVING, (float)0 }, |
|
{ TASK_FACE_PLAYER, (float)0.5 }, |
|
}; |
|
|
|
Schedule_t slMoveAway[] = |
|
{ |
|
{ |
|
tlMoveAway, |
|
ARRAYSIZE( tlMoveAway ), |
|
0, |
|
0, |
|
"MoveAway" |
|
}, |
|
}; |
|
|
|
Task_t tlMoveAwayFail[] = |
|
{ |
|
{ TASK_STOP_MOVING, (float)0 }, |
|
{ TASK_FACE_PLAYER, (float)0.5 }, |
|
}; |
|
|
|
Schedule_t slMoveAwayFail[] = |
|
{ |
|
{ |
|
tlMoveAwayFail, |
|
ARRAYSIZE( tlMoveAwayFail ), |
|
0, |
|
0, |
|
"MoveAwayFail" |
|
}, |
|
}; |
|
|
|
Task_t tlMoveAwayFollow[] = |
|
{ |
|
{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_FACE }, |
|
{ TASK_STORE_LASTPOSITION, (float)0 }, |
|
{ TASK_MOVE_AWAY_PATH, (float)100 }, |
|
{ TASK_WALK_PATH_FOR_UNITS, (float)100 }, |
|
{ TASK_STOP_MOVING, (float)0 }, |
|
{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, |
|
}; |
|
|
|
Schedule_t slMoveAwayFollow[] = |
|
{ |
|
{ |
|
tlMoveAwayFollow, |
|
ARRAYSIZE( tlMoveAwayFollow ), |
|
0, |
|
0, |
|
"MoveAwayFollow" |
|
}, |
|
}; |
|
|
|
Task_t tlTlkIdleWatchClient[] = |
|
{ |
|
{ TASK_STOP_MOVING, 0 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
{ TASK_TLK_LOOK_AT_CLIENT, (float)6 }, |
|
}; |
|
|
|
Task_t tlTlkIdleWatchClientStare[] = |
|
{ |
|
{ TASK_STOP_MOVING, 0 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
{ TASK_TLK_CLIENT_STARE, (float)6 }, |
|
{ TASK_TLK_STARE, (float)0 }, |
|
{ TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
{ TASK_TLK_EYECONTACT, (float)0 }, |
|
}; |
|
|
|
Schedule_t slTlkIdleWatchClient[] = |
|
{ |
|
{ |
|
tlTlkIdleWatchClient, |
|
ARRAYSIZE( tlTlkIdleWatchClient ), |
|
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_CLIENT_UNSEEN | |
|
bits_COND_PROVOKED, |
|
bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. |
|
//bits_SOUND_PLAYER | |
|
//bits_SOUND_WORLD | |
|
bits_SOUND_DANGER | |
|
bits_SOUND_MEAT |// scents |
|
bits_SOUND_CARCASS | |
|
bits_SOUND_GARBAGE, |
|
"TlkIdleWatchClient" |
|
}, |
|
|
|
{ |
|
tlTlkIdleWatchClientStare, |
|
ARRAYSIZE( tlTlkIdleWatchClientStare ), |
|
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_CLIENT_UNSEEN | |
|
bits_COND_PROVOKED, |
|
bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. |
|
//bits_SOUND_PLAYER | |
|
//bits_SOUND_WORLD | |
|
bits_SOUND_DANGER | |
|
bits_SOUND_MEAT |// scents |
|
bits_SOUND_CARCASS | |
|
bits_SOUND_GARBAGE, |
|
"TlkIdleWatchClientStare" |
|
}, |
|
}; |
|
|
|
Task_t tlTlkIdleEyecontact[] = |
|
{ |
|
{ TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
{ TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done |
|
}; |
|
|
|
Schedule_t slTlkIdleEyecontact[] = |
|
{ |
|
{ |
|
tlTlkIdleEyecontact, |
|
ARRAYSIZE( tlTlkIdleEyecontact ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_CLIENT_PUSH | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"TlkIdleEyecontact" |
|
}, |
|
}; |
|
|
|
DEFINE_CUSTOM_SCHEDULES( CTalkMonster ) |
|
{ |
|
slIdleResponse, |
|
slIdleSpeak, |
|
slIdleHello, |
|
slIdleSpeakWait, |
|
slIdleStopShooting, |
|
slMoveAway, |
|
slMoveAwayFollow, |
|
slMoveAwayFail, |
|
slTlkIdleWatchClient, |
|
&slTlkIdleWatchClient[1], |
|
slTlkIdleEyecontact, |
|
}; |
|
|
|
IMPLEMENT_CUSTOM_SCHEDULES( CTalkMonster, CSquadMonster ) |
|
|
|
void CTalkMonster::SetActivity( Activity newActivity ) |
|
{ |
|
// if( newActivity == ACT_IDLE && IsTalking() ) |
|
// newActivity = ACT_SIGNAL3; |
|
|
|
// if( newActivity == ACT_SIGNAL3 && ( LookupActivity( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE ) ) |
|
// newActivity = ACT_IDLE; |
|
|
|
CBaseMonster::SetActivity( newActivity ); |
|
} |
|
|
|
void CTalkMonster::StartTask( Task_t *pTask ) |
|
{ |
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_TLK_SPEAK: |
|
// ask question or make statement |
|
FIdleSpeak(); |
|
TaskComplete(); |
|
break; |
|
case TASK_TLK_RESPOND: |
|
// respond to question |
|
IdleRespond(); |
|
TaskComplete(); |
|
break; |
|
case TASK_TLK_HELLO: |
|
// greet player |
|
FIdleHello(); |
|
TaskComplete(); |
|
break; |
|
case TASK_TLK_STARE: |
|
// let the player know I know he's staring at me. |
|
FIdleStare(); |
|
TaskComplete(); |
|
break; |
|
case TASK_FACE_PLAYER: |
|
case TASK_TLK_LOOK_AT_CLIENT: |
|
case TASK_TLK_CLIENT_STARE: |
|
// track head to the client for a while. |
|
m_flWaitFinished = gpGlobals->time + pTask->flData; |
|
break; |
|
case TASK_TLK_EYECONTACT: |
|
break; |
|
case TASK_TLK_IDEALYAW: |
|
if( m_hTalkTarget != 0 ) |
|
{ |
|
pev->yaw_speed = 60; |
|
float yaw = VecToYaw( m_hTalkTarget->pev->origin - pev->origin ) - pev->angles.y; |
|
|
|
if( yaw > 180 ) |
|
yaw -= 360; |
|
if( yaw < -180 ) |
|
yaw += 360; |
|
|
|
if( yaw < 0 ) |
|
{ |
|
pev->ideal_yaw = Q_min( yaw + 45, 0 ) + pev->angles.y; |
|
} |
|
else |
|
{ |
|
pev->ideal_yaw = Q_max( yaw - 45, 0 ) + pev->angles.y; |
|
} |
|
} |
|
TaskComplete(); |
|
break; |
|
case TASK_TLK_HEADRESET: |
|
// reset head position after looking at something |
|
m_hTalkTarget = NULL; |
|
TaskComplete(); |
|
break; |
|
case TASK_TLK_STOPSHOOTING: |
|
// tell player to stop shooting |
|
PlaySentence( m_szGrp[TLK_NOSHOOT], RANDOM_FLOAT( 2.8, 3.2 ), VOL_NORM, ATTN_NORM ); |
|
TaskComplete(); |
|
break; |
|
case TASK_CANT_FOLLOW: |
|
StopFollowing( FALSE ); |
|
PlaySentence( m_szGrp[TLK_STOP], RANDOM_FLOAT( 2, 2.5 ), VOL_NORM, ATTN_NORM ); |
|
TaskComplete(); |
|
break; |
|
case TASK_WALK_PATH_FOR_UNITS: |
|
m_movementActivity = ACT_WALK; |
|
break; |
|
case TASK_MOVE_AWAY_PATH: |
|
{ |
|
Vector dir = pev->angles; |
|
dir.y = pev->ideal_yaw + 180; |
|
Vector move; |
|
|
|
UTIL_MakeVectorsPrivate( dir, move, NULL, NULL ); |
|
dir = pev->origin + move * pTask->flData; |
|
if( MoveToLocation( ACT_WALK, 2, dir ) ) |
|
{ |
|
TaskComplete(); |
|
} |
|
else if( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) |
|
{ |
|
// then try for plain ole cover |
|
m_flMoveWaitFinished = gpGlobals->time + 2; |
|
TaskComplete(); |
|
} |
|
else |
|
{ |
|
// nowhere to go? |
|
TaskFail(); |
|
} |
|
} |
|
break; |
|
case TASK_PLAY_SCRIPT: |
|
m_hTalkTarget = NULL; |
|
CBaseMonster::StartTask( pTask ); |
|
break; |
|
default: |
|
CBaseMonster::StartTask( pTask ); |
|
} |
|
} |
|
|
|
void CTalkMonster::RunTask( Task_t *pTask ) |
|
{ |
|
edict_t *pPlayer; |
|
|
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_TLK_CLIENT_STARE: |
|
case TASK_TLK_LOOK_AT_CLIENT: |
|
// track head to the client for a while. |
|
if( m_MonsterState == MONSTERSTATE_IDLE && |
|
!IsMoving() && |
|
!IsTalking() ) |
|
{ |
|
// Get edict for one player |
|
pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); |
|
|
|
if( pPlayer ) |
|
{ |
|
IdleHeadTurn( pPlayer->v.origin ); |
|
} |
|
} |
|
else |
|
{ |
|
// started moving or talking |
|
TaskFail(); |
|
return; |
|
} |
|
|
|
if( pTask->iTask == TASK_TLK_CLIENT_STARE ) |
|
{ |
|
// fail out if the player looks away or moves away. |
|
if( ( pPlayer->v.origin - pev->origin ).Length2D() > TLK_STARE_DIST ) |
|
{ |
|
// player moved away. |
|
TaskFail(); |
|
} |
|
|
|
UTIL_MakeVectors( pPlayer->v.angles ); |
|
if( UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) < m_flFieldOfView ) |
|
{ |
|
// player looked away |
|
TaskFail(); |
|
} |
|
} |
|
|
|
if( gpGlobals->time > m_flWaitFinished ) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
case TASK_FACE_PLAYER: |
|
{ |
|
// Get edict for one player |
|
pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); |
|
|
|
if( pPlayer ) |
|
{ |
|
MakeIdealYaw( pPlayer->v.origin ); |
|
ChangeYaw( pev->yaw_speed ); |
|
IdleHeadTurn( pPlayer->v.origin ); |
|
if( gpGlobals->time > m_flWaitFinished && FlYawDiff() < 10 ) |
|
{ |
|
TaskComplete(); |
|
} |
|
} |
|
else |
|
{ |
|
TaskFail(); |
|
} |
|
} |
|
break; |
|
case TASK_TLK_EYECONTACT: |
|
if( !IsMoving() && IsTalking() && m_hTalkTarget != 0 ) |
|
{ |
|
// ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time ); |
|
IdleHeadTurn( m_hTalkTarget->pev->origin ); |
|
} |
|
else |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
case TASK_WALK_PATH_FOR_UNITS: |
|
{ |
|
float distance; |
|
|
|
distance = ( m_vecLastPosition - pev->origin ).Length2D(); |
|
|
|
// Walk path until far enough away |
|
if( distance > pTask->flData || MovementIsComplete() ) |
|
{ |
|
TaskComplete(); |
|
RouteClear(); // Stop moving |
|
} |
|
} |
|
break; |
|
case TASK_WAIT_FOR_MOVEMENT: |
|
if( IsTalking() && m_hTalkTarget != 0 ) |
|
{ |
|
// ALERT( at_console, "walking, talking\n" ); |
|
IdleHeadTurn( m_hTalkTarget->pev->origin ); |
|
} |
|
else |
|
{ |
|
IdleHeadTurn( pev->origin ); |
|
// override so that during walk, a scientist may talk and greet player |
|
FIdleHello(); |
|
if( RANDOM_LONG( 0, m_nSpeak * 20 ) == 0) |
|
{ |
|
FIdleSpeak(); |
|
} |
|
} |
|
|
|
CBaseMonster::RunTask( pTask ); |
|
if( TaskIsComplete() ) |
|
IdleHeadTurn( pev->origin ); |
|
break; |
|
default: |
|
if( IsTalking() && m_hTalkTarget != 0 ) |
|
{ |
|
IdleHeadTurn( m_hTalkTarget->pev->origin ); |
|
} |
|
else |
|
{ |
|
SetBoneController( 0, 0 ); |
|
} |
|
CBaseMonster::RunTask( pTask ); |
|
} |
|
} |
|
|
|
void CTalkMonster::Killed( entvars_t *pevAttacker, int iGib ) |
|
{ |
|
// If a client killed me (unless I was already Barnacle'd), make everyone else mad/afraid of him |
|
if( ( pevAttacker->flags & FL_CLIENT) && m_MonsterState != MONSTERSTATE_PRONE ) |
|
{ |
|
AlertFriends(); |
|
LimitFollowers( CBaseEntity::Instance( pevAttacker ), 0 ); |
|
} |
|
|
|
m_hTargetEnt = 0; |
|
// Don't finish that sentence |
|
StopTalking(); |
|
SetUse( NULL ); |
|
CSquadMonster::Killed( pevAttacker, iGib ); |
|
} |
|
|
|
CBaseEntity *CTalkMonster::EnumFriends( CBaseEntity *pPrevious, int listNumber, BOOL bTrace ) |
|
{ |
|
CBaseEntity *pFriend = pPrevious; |
|
const char *pszFriend; |
|
TraceResult tr; |
|
Vector vecCheck; |
|
|
|
pszFriend = FriendByNumber( listNumber ); |
|
while( ( pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend ) ) ) |
|
{ |
|
if( pFriend == this || !pFriend->IsAlive() ) |
|
// don't talk to self or dead people |
|
continue; |
|
if( bTrace ) |
|
{ |
|
vecCheck = pFriend->pev->origin; |
|
vecCheck.z = pFriend->pev->absmax.z; |
|
|
|
UTIL_TraceLine( pev->origin, vecCheck, ignore_monsters, ENT( pev ), &tr ); |
|
} |
|
else |
|
tr.flFraction = 1.0; |
|
|
|
if( tr.flFraction == 1.0 ) |
|
{ |
|
return pFriend; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void CTalkMonster::AlertFriends( void ) |
|
{ |
|
CBaseEntity *pFriend = NULL; |
|
int i; |
|
|
|
// for each friend in this bsp... |
|
for( i = 0; i < NumberOfFriends(); i++ ) |
|
{ |
|
while( ( pFriend = EnumFriends( pFriend, i, TRUE ) ) ) |
|
{ |
|
CBaseMonster *pMonster = pFriend->MyMonsterPointer(); |
|
if( pMonster->IsAlive() ) |
|
{ |
|
// don't provoke a friend that's playing a death animation. They're a goner |
|
pMonster->m_afMemory |= bits_MEMORY_PROVOKED; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CTalkMonster::ShutUpFriends( void ) |
|
{ |
|
CBaseEntity *pFriend = NULL; |
|
int i; |
|
|
|
// for each friend in this bsp... |
|
for( i = 0; i < NumberOfFriends(); i++ ) |
|
{ |
|
while( ( pFriend = EnumFriends( pFriend, i, TRUE ) ) ) |
|
{ |
|
CBaseMonster *pMonster = pFriend->MyMonsterPointer(); |
|
if( pMonster ) |
|
{ |
|
pMonster->SentenceStop(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// UNDONE: Keep a follow time in each follower, make a list of followers in this function and do LRU |
|
// UNDONE: Check this in Restore to keep restored monsters from joining a full list of followers |
|
void CTalkMonster::LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ) |
|
{ |
|
CBaseEntity *pFriend = NULL; |
|
int i, count; |
|
|
|
count = 0; |
|
|
|
// for each friend in this bsp... |
|
for( i = 0; i < NumberOfFriends(); i++ ) |
|
{ |
|
while( ( pFriend = EnumFriends( pFriend, i, FALSE ) ) ) |
|
{ |
|
CBaseMonster *pMonster = pFriend->MyMonsterPointer(); |
|
if( pMonster ) |
|
{ |
|
if( pMonster->m_hTargetEnt == pPlayer ) |
|
{ |
|
count++; |
|
if( count > maxFollowers ) |
|
pMonster->StopFollowing( TRUE ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
float CTalkMonster::TargetDistance( void ) |
|
{ |
|
// If we lose the player, or he dies, return a really large distance |
|
if( m_hTargetEnt == 0 || !m_hTargetEnt->IsAlive() ) |
|
return 1e6; |
|
|
|
return ( m_hTargetEnt->pev->origin - pev->origin ).Length(); |
|
} |
|
|
|
//========================================================= |
|
// HandleAnimEvent - catches the monster-specific messages |
|
// that occur when tagged animation frames are played. |
|
//========================================================= |
|
void CTalkMonster::HandleAnimEvent( MonsterEvent_t *pEvent ) |
|
{ |
|
switch( pEvent->event ) |
|
{ |
|
case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 25% of the time |
|
if( RANDOM_LONG( 0, 99 ) < 75 ) |
|
break; |
|
// fall through... |
|
case SCRIPT_EVENT_SENTENCE: // Play a named sentence group |
|
ShutUpFriends(); |
|
PlaySentence( pEvent->options, RANDOM_FLOAT( 2.8, 3.4 ), VOL_NORM, ATTN_IDLE ); |
|
//ALERT( at_console, "script event speak\n" ); |
|
break; |
|
default: |
|
CBaseMonster::HandleAnimEvent( pEvent ); |
|
break; |
|
} |
|
} |
|
|
|
// monsters derived from ctalkmonster should call this in precache() |
|
void CTalkMonster::TalkInit( void ) |
|
{ |
|
// every new talking monster must reset this global, otherwise |
|
// when a level is loaded, nobody will talk (time is reset to 0) |
|
CTalkMonster::g_talkWaitTime = 0; |
|
|
|
m_voicePitch = 100; |
|
} |
|
//========================================================= |
|
// FindNearestFriend |
|
// Scan for nearest, visible friend. If fPlayer is true, look for |
|
// nearest player |
|
//========================================================= |
|
CBaseEntity *CTalkMonster::FindNearestFriend( BOOL fPlayer ) |
|
{ |
|
CBaseEntity *pFriend = NULL; |
|
CBaseEntity *pNearest = NULL; |
|
float range = 10000000.0; |
|
TraceResult tr; |
|
Vector vecStart = pev->origin; |
|
Vector vecCheck; |
|
int i; |
|
const char *pszFriend; |
|
int cfriends; |
|
|
|
vecStart.z = pev->absmax.z; |
|
|
|
if( fPlayer ) |
|
cfriends = 1; |
|
else |
|
cfriends = NumberOfFriends(); |
|
|
|
// for each type of friend... |
|
for( i = cfriends-1; i > -1; i-- ) |
|
{ |
|
if( fPlayer ) |
|
pszFriend = "player"; |
|
else |
|
pszFriend = FriendByNumber( i ); |
|
|
|
if( !pszFriend ) |
|
continue; |
|
|
|
// for each friend in this bsp... |
|
while( ( pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend ) ) ) |
|
{ |
|
if( pFriend == this || !pFriend->IsAlive() ) |
|
// don't talk to self or dead people |
|
continue; |
|
|
|
CBaseMonster *pMonster = pFriend->MyMonsterPointer(); |
|
|
|
// If not a monster for some reason, or in a script, or prone |
|
if( !pMonster || pMonster->m_MonsterState == MONSTERSTATE_SCRIPT || pMonster->m_MonsterState == MONSTERSTATE_PRONE ) |
|
continue; |
|
|
|
vecCheck = pFriend->pev->origin; |
|
vecCheck.z = pFriend->pev->absmax.z; |
|
|
|
// if closer than previous friend, and in range, see if he's visible |
|
if( range > ( vecStart - vecCheck ).Length()) |
|
{ |
|
UTIL_TraceLine( vecStart, vecCheck, ignore_monsters, ENT( pev ), &tr ); |
|
|
|
if( tr.flFraction == 1.0 ) |
|
{ |
|
// visible and in range, this is the new nearest scientist |
|
if( ( vecStart - vecCheck ).Length() < TALKRANGE_MIN ) |
|
{ |
|
pNearest = pFriend; |
|
range = ( vecStart - vecCheck ).Length(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
return pNearest; |
|
} |
|
|
|
int CTalkMonster::GetVoicePitch( void ) |
|
{ |
|
return m_voicePitch + RANDOM_LONG( 0, 3 ); |
|
} |
|
|
|
void CTalkMonster::Touch( CBaseEntity *pOther ) |
|
{ |
|
// Did the player touch me? |
|
if( pOther->IsPlayer() ) |
|
{ |
|
// Ignore if pissed at player |
|
if( m_afMemory & bits_MEMORY_PROVOKED ) |
|
return; |
|
|
|
// Stay put during speech |
|
if( IsTalking() ) |
|
return; |
|
|
|
// Heuristic for determining if the player is pushing me away |
|
float speed = fabs( pOther->pev->velocity.x ) + fabs( pOther->pev->velocity.y ); |
|
if( speed > 50 ) |
|
{ |
|
SetConditions( bits_COND_CLIENT_PUSH ); |
|
MakeIdealYaw( pOther->pev->origin ); |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// IdleRespond |
|
// Respond to a previous question |
|
//========================================================= |
|
void CTalkMonster::IdleRespond( void ) |
|
{ |
|
//int pitch = GetVoicePitch(); |
|
|
|
// play response |
|
PlaySentence( m_szGrp[TLK_ANSWER], RANDOM_FLOAT( 2.8, 3.2 ), VOL_NORM, ATTN_IDLE ); |
|
} |
|
|
|
int CTalkMonster::FOkToSpeak( void ) |
|
{ |
|
// if in the grip of a barnacle, don't speak |
|
if( m_MonsterState == MONSTERSTATE_PRONE || m_IdealMonsterState == MONSTERSTATE_PRONE ) |
|
{ |
|
return FALSE; |
|
} |
|
|
|
// if not alive, certainly don't speak |
|
if( pev->deadflag != DEAD_NO ) |
|
{ |
|
return FALSE; |
|
} |
|
|
|
// if someone else is talking, don't speak |
|
if( gpGlobals->time <= CTalkMonster::g_talkWaitTime ) |
|
return FALSE; |
|
|
|
if( pev->spawnflags & SF_MONSTER_GAG ) |
|
return FALSE; |
|
|
|
if( m_MonsterState == MONSTERSTATE_PRONE ) |
|
return FALSE; |
|
|
|
// if player is not in pvs, don't speak |
|
if( !IsAlive() || FNullEnt(FIND_CLIENT_IN_PVS( edict() ) ) ) |
|
return FALSE; |
|
|
|
// don't talk if you're in combat |
|
if( m_hEnemy != 0 && FVisible( m_hEnemy ) ) |
|
return FALSE; |
|
|
|
return TRUE; |
|
} |
|
|
|
int CTalkMonster::CanPlaySentence( BOOL fDisregardState ) |
|
{ |
|
if( fDisregardState ) |
|
return CBaseMonster::CanPlaySentence( fDisregardState ); |
|
return FOkToSpeak(); |
|
} |
|
|
|
//========================================================= |
|
// FIdleStare |
|
//========================================================= |
|
int CTalkMonster::FIdleStare( void ) |
|
{ |
|
if( !FOkToSpeak() ) |
|
return FALSE; |
|
|
|
PlaySentence( m_szGrp[TLK_STARE], RANDOM_FLOAT(5, 7.5), VOL_NORM, ATTN_IDLE ); |
|
|
|
m_hTalkTarget = FindNearestFriend( TRUE ); |
|
return TRUE; |
|
} |
|
|
|
//========================================================= |
|
// IdleHello |
|
// Try to greet player first time he's seen |
|
//========================================================= |
|
int CTalkMonster::FIdleHello( void ) |
|
{ |
|
if( !FOkToSpeak() ) |
|
return FALSE; |
|
|
|
// if this is first time scientist has seen player, greet him |
|
if( !FBitSet( m_bitsSaid, bit_saidHelloPlayer ) ) |
|
{ |
|
// get a player |
|
CBaseEntity *pPlayer = FindNearestFriend( TRUE ); |
|
|
|
if( pPlayer ) |
|
{ |
|
if( FInViewCone( pPlayer ) && FVisible( pPlayer ) ) |
|
{ |
|
m_hTalkTarget = pPlayer; |
|
|
|
if( FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER ) ) |
|
PlaySentence( m_szGrp[TLK_PHELLO], RANDOM_FLOAT( 3, 3.5 ), VOL_NORM, ATTN_IDLE ); |
|
else |
|
PlaySentence( m_szGrp[TLK_HELLO], RANDOM_FLOAT( 3, 3.5 ), VOL_NORM, ATTN_IDLE ); |
|
|
|
SetBits( m_bitsSaid, bit_saidHelloPlayer ); |
|
|
|
return TRUE; |
|
} |
|
} |
|
} |
|
return FALSE; |
|
} |
|
|
|
// turn head towards supplied origin |
|
void CTalkMonster::IdleHeadTurn( Vector &vecFriend ) |
|
{ |
|
// turn head in desired direction only if ent has a turnable head |
|
if( m_afCapability & bits_CAP_TURN_HEAD ) |
|
{ |
|
float yaw = VecToYaw( vecFriend - pev->origin ) - pev->angles.y; |
|
|
|
if( yaw > 180 ) |
|
yaw -= 360; |
|
if( yaw < -180 ) |
|
yaw += 360; |
|
|
|
// turn towards vector |
|
SetBoneController( 0, yaw ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// FIdleSpeak |
|
// ask question of nearby friend, or make statement |
|
//========================================================= |
|
int CTalkMonster::FIdleSpeak( void ) |
|
{ |
|
// try to start a conversation, or make statement |
|
//int pitch; |
|
const char *szIdleGroup; |
|
const char *szQuestionGroup; |
|
float duration; |
|
|
|
if( !FOkToSpeak() ) |
|
return FALSE; |
|
|
|
// set idle groups based on pre/post disaster |
|
if( FBitSet( pev->spawnflags, SF_MONSTER_PREDISASTER ) ) |
|
{ |
|
szIdleGroup = m_szGrp[TLK_PIDLE]; |
|
szQuestionGroup = m_szGrp[TLK_PQUESTION]; |
|
// set global min delay for next conversation |
|
duration = RANDOM_FLOAT( 4.8, 5.2 ); |
|
} |
|
else |
|
{ |
|
szIdleGroup = m_szGrp[TLK_IDLE]; |
|
szQuestionGroup = m_szGrp[TLK_QUESTION]; |
|
// set global min delay for next conversation |
|
duration = RANDOM_FLOAT( 2.8, 3.2 ); |
|
} |
|
|
|
//pitch = GetVoicePitch(); |
|
|
|
// player using this entity is alive and wounded? |
|
CBaseEntity *pTarget = m_hTargetEnt; |
|
|
|
if( pTarget != NULL ) |
|
{ |
|
if( pTarget->IsPlayer() ) |
|
{ |
|
if( pTarget->IsAlive() ) |
|
{ |
|
m_hTalkTarget = m_hTargetEnt; |
|
if( !FBitSet(m_bitsSaid, bit_saidDamageHeavy ) && |
|
( m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 8 ) ) |
|
{ |
|
//EMIT_SOUND_DYN(ENT( pev ), CHAN_VOICE, m_szGrp[TLK_PLHURT3], 1.0, ATTN_IDLE, 0, pitch ); |
|
PlaySentence( m_szGrp[TLK_PLHURT3], duration, VOL_NORM, ATTN_IDLE ); |
|
SetBits( m_bitsSaid, bit_saidDamageHeavy ); |
|
return TRUE; |
|
} |
|
else if( !FBitSet( m_bitsSaid, bit_saidDamageMedium ) && |
|
( m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 4 ) ) |
|
{ |
|
//EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT2], 1.0, ATTN_IDLE, 0, pitch ); |
|
PlaySentence( m_szGrp[TLK_PLHURT2], duration, VOL_NORM, ATTN_IDLE ); |
|
SetBits( m_bitsSaid, bit_saidDamageMedium ); |
|
return TRUE; |
|
} |
|
else if( !FBitSet( m_bitsSaid, bit_saidDamageLight) && |
|
( m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 2 ) ) |
|
{ |
|
//EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, m_szGrp[TLK_PLHURT1], 1.0, ATTN_IDLE, 0, pitch ); |
|
PlaySentence( m_szGrp[TLK_PLHURT1], duration, VOL_NORM, ATTN_IDLE ); |
|
SetBits( m_bitsSaid, bit_saidDamageLight ); |
|
return TRUE; |
|
} |
|
} |
|
else |
|
{ |
|
//!!!KELLY - here's a cool spot to have the talkmonster talk about the dead player if we want. |
|
// "Oh dear, Gordon Freeman is dead!" -Scientist |
|
// "Damn, I can't do this without you." -Barney |
|
} |
|
} |
|
} |
|
|
|
// if there is a friend nearby to speak to, play sentence, set friend's response time, return |
|
CBaseEntity *pFriend = FindNearestFriend( FALSE ); |
|
|
|
if( pFriend && !( pFriend->IsMoving() ) && ( RANDOM_LONG( 0, 99 ) < 75 ) ) |
|
{ |
|
PlaySentence( szQuestionGroup, duration, VOL_NORM, ATTN_IDLE ); |
|
//SENTENCEG_PlayRndSz( ENT( pev ), szQuestionGroup, 1.0, ATTN_IDLE, 0, pitch ); |
|
|
|
// force friend to answer |
|
CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; |
|
m_hTalkTarget = pFriend; |
|
pTalkMonster->SetAnswerQuestion( this ); // UNDONE: This is EVIL!!! |
|
pTalkMonster->m_flStopTalkTime = m_flStopTalkTime; |
|
|
|
m_nSpeak++; |
|
return TRUE; |
|
} |
|
|
|
// otherwise, play an idle statement, try to face client when making a statement. |
|
if( RANDOM_LONG( 0, 1 ) ) |
|
{ |
|
//SENTENCEG_PlayRndSz( ENT( pev ), szIdleGroup, 1.0, ATTN_IDLE, 0, pitch ); |
|
pFriend = FindNearestFriend( TRUE ); |
|
|
|
if( pFriend ) |
|
{ |
|
m_hTalkTarget = pFriend; |
|
PlaySentence( szIdleGroup, duration, VOL_NORM, ATTN_IDLE ); |
|
m_nSpeak++; |
|
return TRUE; |
|
} |
|
} |
|
|
|
// didn't speak |
|
Talk( 0 ); |
|
CTalkMonster::g_talkWaitTime = 0; |
|
return FALSE; |
|
} |
|
|
|
void CTalkMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) |
|
{ |
|
if( !bConcurrent ) |
|
ShutUpFriends(); |
|
|
|
ClearConditions( bits_COND_CLIENT_PUSH ); // Forget about moving! I've got something to say! |
|
m_useTime = gpGlobals->time + duration; |
|
PlaySentence( pszSentence, duration, volume, attenuation ); |
|
|
|
m_hTalkTarget = pListener; |
|
} |
|
|
|
void CTalkMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) |
|
{ |
|
if( !pszSentence ) |
|
return; |
|
|
|
Talk( duration ); |
|
|
|
CTalkMonster::g_talkWaitTime = gpGlobals->time + duration + 2.0; |
|
if( pszSentence[0] == '!' ) |
|
EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, GetVoicePitch() ); |
|
else |
|
SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, GetVoicePitch() ); |
|
|
|
// If you say anything, don't greet the player - you may have already spoken to them |
|
SetBits( m_bitsSaid, bit_saidHelloPlayer ); |
|
} |
|
|
|
//========================================================= |
|
// Talk - set a timer that tells us when the monster is done |
|
// talking. |
|
//========================================================= |
|
void CTalkMonster::Talk( float flDuration ) |
|
{ |
|
if( flDuration <= 0 ) |
|
{ |
|
// no duration :( |
|
m_flStopTalkTime = gpGlobals->time + 3; |
|
} |
|
else |
|
{ |
|
m_flStopTalkTime = gpGlobals->time + flDuration; |
|
} |
|
} |
|
|
|
// Prepare this talking monster to answer question |
|
void CTalkMonster::SetAnswerQuestion( CTalkMonster *pSpeaker ) |
|
{ |
|
if( !m_pCine ) |
|
ChangeSchedule( slIdleResponse ); |
|
m_hTalkTarget = (CBaseMonster *)pSpeaker; |
|
} |
|
|
|
int CTalkMonster::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) |
|
{ |
|
if( IsAlive() ) |
|
{ |
|
// if player damaged this entity, have other friends talk about it |
|
if( pevAttacker && m_MonsterState != MONSTERSTATE_PRONE && FBitSet( pevAttacker->flags, FL_CLIENT ) ) |
|
{ |
|
CBaseEntity *pFriend = FindNearestFriend( FALSE ); |
|
|
|
if( pFriend && pFriend->IsAlive() ) |
|
{ |
|
// only if not dead or dying! |
|
CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; |
|
pTalkMonster->ChangeSchedule( slIdleStopShooting ); |
|
} |
|
} |
|
} |
|
return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); |
|
} |
|
|
|
Schedule_t *CTalkMonster::GetScheduleOfType( int Type ) |
|
{ |
|
switch( Type ) |
|
{ |
|
case SCHED_MOVE_AWAY: |
|
return slMoveAway; |
|
case SCHED_MOVE_AWAY_FOLLOW: |
|
return slMoveAwayFollow; |
|
case SCHED_MOVE_AWAY_FAIL: |
|
return slMoveAwayFail; |
|
case SCHED_TARGET_FACE: |
|
// speak during 'use' |
|
if( RANDOM_LONG( 0, 99 ) < 2 ) |
|
//ALERT( at_console, "target chase speak\n" ); |
|
return slIdleSpeakWait; |
|
else |
|
return slIdleStand; |
|
case SCHED_IDLE_STAND: |
|
{ |
|
// if never seen player, try to greet him |
|
if( !FBitSet( m_bitsSaid, bit_saidHelloPlayer ) ) |
|
{ |
|
return slIdleHello; |
|
} |
|
|
|
// sustained light wounds? |
|
if( !FBitSet( m_bitsSaid, bit_saidWoundLight ) && ( pev->health <= ( pev->max_health * 0.75 ) ) ) |
|
{ |
|
//SENTENCEG_PlayRndSz( ENT( pev ), m_szGrp[TLK_WOUND], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); |
|
//CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT( 2.8, 3.2 ); |
|
PlaySentence( m_szGrp[TLK_WOUND], RANDOM_FLOAT( 2.8, 3.2 ), VOL_NORM, ATTN_IDLE ); |
|
SetBits( m_bitsSaid, bit_saidWoundLight ); |
|
return slIdleStand; |
|
} |
|
// sustained heavy wounds? |
|
else if( !FBitSet( m_bitsSaid, bit_saidWoundHeavy ) && ( pev->health <= ( pev->max_health * 0.5 ) ) ) |
|
{ |
|
//SENTENCEG_PlayRndSz( ENT( pev ), m_szGrp[TLK_MORTAL], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); |
|
//CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT( 2.8, 3.2 ); |
|
PlaySentence( m_szGrp[TLK_MORTAL], RANDOM_FLOAT( 2.8, 3.2 ), VOL_NORM, ATTN_IDLE ); |
|
SetBits( m_bitsSaid, bit_saidWoundHeavy ); |
|
return slIdleStand; |
|
} |
|
|
|
// talk about world |
|
if( FOkToSpeak() && RANDOM_LONG( 0, m_nSpeak * 2 ) == 0 ) |
|
{ |
|
//ALERT ( at_console, "standing idle speak\n" ); |
|
return slIdleSpeak; |
|
} |
|
|
|
if( !IsTalking() && HasConditions( bits_COND_SEE_CLIENT ) && RANDOM_LONG( 0, 6 ) == 0 ) |
|
{ |
|
edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); |
|
|
|
if( pPlayer ) |
|
{ |
|
// watch the client. |
|
UTIL_MakeVectors( pPlayer->v.angles ); |
|
if( ( pPlayer->v.origin - pev->origin ).Length2D() < TLK_STARE_DIST && |
|
UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) >= m_flFieldOfView ) |
|
{ |
|
// go into the special STARE schedule if the player is close, and looking at me too. |
|
return &slTlkIdleWatchClient[1]; |
|
} |
|
|
|
return slTlkIdleWatchClient; |
|
} |
|
} |
|
else |
|
{ |
|
if( IsTalking() ) |
|
// look at who we're talking to |
|
return slTlkIdleEyecontact; |
|
else |
|
// regular standing idle |
|
return slIdleStand; |
|
} |
|
|
|
// NOTE - caller must first CTalkMonster::GetScheduleOfType, |
|
// then check result and decide what to return ie: if sci gets back |
|
// slIdleStand, return slIdleSciStand |
|
} |
|
break; |
|
} |
|
|
|
return CSquadMonster::GetScheduleOfType( Type ); |
|
} |
|
|
|
//========================================================= |
|
// IsTalking - am I saying a sentence right now? |
|
//========================================================= |
|
BOOL CTalkMonster::IsTalking( void ) |
|
{ |
|
if( m_flStopTalkTime > gpGlobals->time ) |
|
{ |
|
return TRUE; |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
//========================================================= |
|
// If there's a player around, watch him. |
|
//========================================================= |
|
void CTalkMonster::PrescheduleThink( void ) |
|
{ |
|
if( !HasConditions( bits_COND_SEE_CLIENT ) ) |
|
{ |
|
SetConditions( bits_COND_CLIENT_UNSEEN ); |
|
} |
|
} |
|
|
|
// try to smell something |
|
void CTalkMonster::TrySmellTalk( void ) |
|
{ |
|
if( !FOkToSpeak() ) |
|
return; |
|
|
|
// clear smell bits periodically |
|
if( gpGlobals->time > m_flLastSaidSmelled ) |
|
{ |
|
//ALERT( at_aiconsole, "Clear smell bits\n" ); |
|
ClearBits( m_bitsSaid, bit_saidSmelled ); |
|
} |
|
|
|
// smelled something? |
|
if( !FBitSet( m_bitsSaid, bit_saidSmelled ) && HasConditions( bits_COND_SMELL ) ) |
|
{ |
|
PlaySentence( m_szGrp[TLK_SMELL], RANDOM_FLOAT( 2.8, 3.2 ), VOL_NORM, ATTN_IDLE ); |
|
m_flLastSaidSmelled = gpGlobals->time + 60;// don't talk about the stinky for a while. |
|
SetBits( m_bitsSaid, bit_saidSmelled ); |
|
} |
|
} |
|
|
|
void CTalkMonster::StartMonster() |
|
{ |
|
CSquadMonster::StartMonster(); |
|
if (m_fStartSuspicious) { |
|
ALERT(at_console, "Talk Monster Pre-Provoked\n"); |
|
Remember(bits_MEMORY_PROVOKED); |
|
} |
|
} |
|
|
|
int CTalkMonster::IRelationship( CBaseEntity *pTarget ) |
|
{ |
|
if( pTarget->IsPlayer() ) |
|
if( m_afMemory & bits_MEMORY_PROVOKED ) |
|
return R_HT; |
|
return CBaseMonster::IRelationship( pTarget ); |
|
} |
|
|
|
void CTalkMonster::StopFollowing( BOOL clearSchedule ) |
|
{ |
|
if( IsFollowing() ) |
|
{ |
|
if( !( m_afMemory & bits_MEMORY_PROVOKED ) ) |
|
{ |
|
PlaySentence( m_szGrp[TLK_UNUSE], RANDOM_FLOAT( 2.8, 3.2 ), VOL_NORM, ATTN_IDLE ); |
|
m_hTalkTarget = m_hTargetEnt; |
|
} |
|
|
|
if( m_movementGoal == MOVEGOAL_TARGETENT ) |
|
RouteClear(); // Stop him from walking toward the player |
|
m_hTargetEnt = 0; |
|
if( clearSchedule ) |
|
ClearSchedule(); |
|
if( m_hEnemy != 0 ) |
|
m_IdealMonsterState = MONSTERSTATE_COMBAT; |
|
} |
|
} |
|
|
|
void CTalkMonster::StartFollowing( CBaseEntity *pLeader ) |
|
{ |
|
if( m_pCine ) |
|
m_pCine->CancelScript(); |
|
|
|
if( m_hEnemy != 0 ) |
|
m_IdealMonsterState = MONSTERSTATE_ALERT; |
|
|
|
m_hTargetEnt = pLeader; |
|
PlaySentence( m_szGrp[TLK_USE], RANDOM_FLOAT( 2.8, 3.2 ), VOL_NORM, ATTN_IDLE ); |
|
m_hTalkTarget = m_hTargetEnt; |
|
ClearConditions( bits_COND_CLIENT_PUSH ); |
|
ClearSchedule(); |
|
} |
|
|
|
BOOL CTalkMonster::CanFollow( void ) |
|
{ |
|
if( m_MonsterState == MONSTERSTATE_SCRIPT ) |
|
{ |
|
if( !m_pCine ) |
|
return FALSE; |
|
if( !m_pCine->CanInterrupt() ) |
|
return FALSE; |
|
} |
|
|
|
if( !IsAlive() ) |
|
return FALSE; |
|
|
|
return !IsFollowing(); |
|
} |
|
|
|
void CTalkMonster::FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
// Don't allow use during a scripted_sentence |
|
if( m_useTime > gpGlobals->time ) |
|
return; |
|
|
|
if( pCaller != NULL && pCaller->IsPlayer() ) |
|
{ |
|
// Pre-disaster followers can't be used |
|
if( pev->spawnflags & SF_MONSTER_PREDISASTER ) |
|
{ |
|
DeclineFollowing(); |
|
} |
|
else if( CanFollow() ) |
|
{ |
|
LimitFollowers( pCaller, MaxFollowers() ); |
|
|
|
if( m_afMemory & bits_MEMORY_PROVOKED ) |
|
ALERT( at_console, "I'm not following you, you evil person!\n" ); |
|
else |
|
{ |
|
StartFollowing( pCaller ); |
|
SetBits( m_bitsSaid, bit_saidHelloPlayer ); // Don't say hi after you've started following |
|
} |
|
} |
|
else |
|
{ |
|
StopFollowing( TRUE ); |
|
} |
|
} |
|
} |
|
|
|
void CTalkMonster::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
if( FStrEq( pkvd->szKeyName, "UseSentence" ) ) |
|
{ |
|
m_iszUse = ALLOC_STRING( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "UnUseSentence" ) ) |
|
{ |
|
m_iszUnUse = ALLOC_STRING( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "suspicious" ) ) |
|
{ |
|
m_fStartSuspicious = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else |
|
CBaseMonster::KeyValue( pkvd ); |
|
} |
|
|
|
void CTalkMonster::Precache( void ) |
|
{ |
|
if( m_iszUse ) |
|
m_szGrp[TLK_USE] = STRING( m_iszUse ); |
|
if( m_iszUnUse ) |
|
m_szGrp[TLK_UNUSE] = STRING( m_iszUnUse ); |
|
}
|
|
|