2
0
mirror of https://github.com/FWGS/hlsdk-xash3d synced 2024-11-10 20:29:35 +01:00
hlsdk-xash3d/dlls/talkmonster.cpp

1434 lines
35 KiB
C++
Raw Normal View History

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