forked from valve/halflife-sdk
1289 lines
29 KiB
C++
1289 lines
29 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2001, 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.
|
|
*
|
|
****/
|
|
//=========================================================
|
|
// schedule.cpp - functions and data pertaining to the
|
|
// monsters' AI scheduling system.
|
|
//=========================================================
|
|
#include "extdll.h"
|
|
#include "util.h"
|
|
#include "cbase.h"
|
|
#include "monsters.h"
|
|
#include "animation.h"
|
|
#include "scripted.h"
|
|
#include "nodes.h"
|
|
#include "soundent.h"
|
|
|
|
extern CGraph WorldGraph;
|
|
|
|
//=========================================================
|
|
// FHaveSchedule - Returns TRUE if monster's m_pSchedule
|
|
// is anything other than NULL.
|
|
//=========================================================
|
|
BOOL CBaseMonster :: FHaveSchedule( void )
|
|
{
|
|
if ( m_pSchedule == NULL )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//=========================================================
|
|
// ClearSchedule - blanks out the caller's schedule pointer
|
|
// and index.
|
|
//=========================================================
|
|
void CBaseMonster :: ClearSchedule( void )
|
|
{
|
|
m_iTaskStatus = TASKSTATUS_NEW;
|
|
m_pSchedule = NULL;
|
|
m_iScheduleIndex = 0;
|
|
}
|
|
|
|
//=========================================================
|
|
// FScheduleDone - Returns TRUE if the caller is on the
|
|
// last task in the schedule
|
|
//=========================================================
|
|
BOOL CBaseMonster :: FScheduleDone ( void )
|
|
{
|
|
ASSERT( m_pSchedule != NULL );
|
|
|
|
if ( m_iScheduleIndex == m_pSchedule->cTasks )
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//=========================================================
|
|
// ChangeSchedule - replaces the monster's schedule pointer
|
|
// with the passed pointer, and sets the ScheduleIndex back
|
|
// to 0
|
|
//=========================================================
|
|
void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule )
|
|
{
|
|
ASSERT( pNewSchedule != NULL );
|
|
|
|
m_pSchedule = pNewSchedule;
|
|
m_iScheduleIndex = 0;
|
|
m_iTaskStatus = TASKSTATUS_NEW;
|
|
m_afConditions = 0;// clear all of the conditions
|
|
m_failSchedule = SCHED_NONE;
|
|
|
|
if ( m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND && !(m_pSchedule->iSoundMask) )
|
|
{
|
|
ALERT ( at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n" );
|
|
}
|
|
else if ( m_pSchedule->iSoundMask && !(m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) )
|
|
{
|
|
ALERT ( at_aiconsole, "Sound mask without COND_HEAR_SOUND!\n" );
|
|
}
|
|
|
|
// this is very useful code if you can isolate a test case in a level with a single monster. It will notify
|
|
// you of every schedule selection the monster makes.
|
|
#if 0
|
|
if ( FClassnameIs( pev, "monster_human_grunt" ) )
|
|
{
|
|
Task_t *pTask = GetTask();
|
|
|
|
if ( pTask )
|
|
{
|
|
const char *pName = NULL;
|
|
|
|
if ( m_pSchedule )
|
|
{
|
|
pName = m_pSchedule->pName;
|
|
}
|
|
else
|
|
{
|
|
pName = "No Schedule";
|
|
}
|
|
|
|
if ( !pName )
|
|
{
|
|
pName = "Unknown";
|
|
}
|
|
|
|
ALERT( at_aiconsole, "%s: picked schedule %s\n", STRING( pev->classname ), pName );
|
|
}
|
|
}
|
|
#endif// 0
|
|
|
|
}
|
|
|
|
//=========================================================
|
|
// NextScheduledTask - increments the ScheduleIndex
|
|
//=========================================================
|
|
void CBaseMonster :: NextScheduledTask ( void )
|
|
{
|
|
ASSERT( m_pSchedule != NULL );
|
|
|
|
m_iTaskStatus = TASKSTATUS_NEW;
|
|
m_iScheduleIndex++;
|
|
|
|
if ( FScheduleDone() )
|
|
{
|
|
// just completed last task in schedule, so make it invalid by clearing it.
|
|
SetConditions( bits_COND_SCHEDULE_DONE );
|
|
//ClearSchedule();
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// IScheduleFlags - returns an integer with all Conditions
|
|
// bits that are currently set and also set in the current
|
|
// schedule's Interrupt mask.
|
|
//=========================================================
|
|
int CBaseMonster :: IScheduleFlags ( void )
|
|
{
|
|
if( !m_pSchedule )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// strip off all bits excepts the ones capable of breaking this schedule.
|
|
return m_afConditions & m_pSchedule->iInterruptMask;
|
|
}
|
|
|
|
//=========================================================
|
|
// FScheduleValid - returns TRUE as long as the current
|
|
// schedule is still the proper schedule to be executing,
|
|
// taking into account all conditions
|
|
//=========================================================
|
|
BOOL CBaseMonster :: FScheduleValid ( void )
|
|
{
|
|
if ( m_pSchedule == NULL )
|
|
{
|
|
// schedule is empty, and therefore not valid.
|
|
return FALSE;
|
|
}
|
|
|
|
if ( HasConditions( m_pSchedule->iInterruptMask | bits_COND_SCHEDULE_DONE | bits_COND_TASK_FAILED ) )
|
|
{
|
|
#ifdef DEBUG
|
|
if ( HasConditions ( bits_COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE )
|
|
{
|
|
// fail! Send a visual indicator.
|
|
ALERT ( at_aiconsole, "Schedule: %s Failed\n", m_pSchedule->pName );
|
|
|
|
Vector tmp = pev->origin;
|
|
tmp.z = pev->absmax.z + 16;
|
|
UTIL_Sparks( tmp );
|
|
}
|
|
#endif // DEBUG
|
|
|
|
// some condition has interrupted the schedule, or the schedule is done
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//=========================================================
|
|
// MaintainSchedule - does all the per-think schedule maintenance.
|
|
// ensures that the monster leaves this function with a valid
|
|
// schedule!
|
|
//=========================================================
|
|
void CBaseMonster :: MaintainSchedule ( void )
|
|
{
|
|
int i;
|
|
|
|
// UNDONE: Tune/fix this 10... This is just here so infinite loops are impossible
|
|
for ( i = 0; i < 10; i++ )
|
|
{
|
|
if ( m_pSchedule != NULL && TaskIsComplete() )
|
|
{
|
|
NextScheduledTask();
|
|
}
|
|
|
|
// validate existing schedule
|
|
if ( !FScheduleValid() || m_MonsterState != m_IdealMonsterState )
|
|
{
|
|
// if we come into this block of code, the schedule is going to have to be changed.
|
|
// if the previous schedule was interrupted by a condition, GetIdealState will be
|
|
// called. Else, a schedule finished normally.
|
|
|
|
// Notify the monster that his schedule is changing
|
|
ScheduleChange();
|
|
|
|
if ( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState )
|
|
{
|
|
// schedule was invalid because the current task failed to start or complete
|
|
ALERT ( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex );
|
|
}
|
|
}
|
|
|
|
if ( m_iTaskStatus == TASKSTATUS_NEW )
|
|
{
|
|
Task_t *pTask = GetTask();
|
|
ASSERT( pTask != NULL );
|
|
TaskBegin();
|
|
StartTask( pTask );
|
|
}
|
|
|
|
// UNDONE: Twice?!!!
|
|
if ( m_Activity != m_IdealActivity )
|
|
{
|
|
SetActivity ( m_IdealActivity );
|
|
}
|
|
|
|
if ( !TaskIsComplete() && m_iTaskStatus != TASKSTATUS_NEW )
|
|
break;
|
|
}
|
|
|
|
if ( TaskIsRunning() )
|
|
{
|
|
Task_t *pTask = GetTask();
|
|
ASSERT( pTask != NULL );
|
|
RunTask( pTask );
|
|
}
|
|
|
|
// UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation
|
|
// RunTask() will always change animations at the end of a script!
|
|
// Don't do this twice
|
|
if ( m_Activity != m_IdealActivity )
|
|
{
|
|
SetActivity ( m_IdealActivity );
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// RunTask
|
|
//=========================================================
|
|
void CBaseMonster :: RunTask ( Task_t *pTask )
|
|
{
|
|
switch ( pTask->iTask )
|
|
{
|
|
case TASK_TURN_RIGHT:
|
|
case TASK_TURN_LEFT:
|
|
{
|
|
ChangeYaw( pev->yaw_speed );
|
|
|
|
if ( FacingIdeal() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TASK_PLAY_SEQUENCE_FACE_ENEMY:
|
|
case TASK_PLAY_SEQUENCE_FACE_TARGET:
|
|
{
|
|
CBaseEntity *pTarget;
|
|
|
|
if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET )
|
|
pTarget = m_hTargetEnt;
|
|
else
|
|
pTarget = m_hEnemy;
|
|
if ( pTarget )
|
|
{
|
|
pev->ideal_yaw = UTIL_VecToYaw( pTarget->pev->origin - pev->origin );
|
|
ChangeYaw( pev->yaw_speed );
|
|
}
|
|
if ( m_fSequenceFinished )
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
|
|
case TASK_PLAY_SEQUENCE:
|
|
case TASK_PLAY_ACTIVE_IDLE:
|
|
{
|
|
if ( m_fSequenceFinished )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
case TASK_FACE_ENEMY:
|
|
{
|
|
MakeIdealYaw( m_vecEnemyLKP );
|
|
|
|
ChangeYaw( pev->yaw_speed );
|
|
|
|
if ( FacingIdeal() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_FACE_HINTNODE:
|
|
case TASK_FACE_LASTPOSITION:
|
|
case TASK_FACE_TARGET:
|
|
case TASK_FACE_IDEAL:
|
|
case TASK_FACE_ROUTE:
|
|
{
|
|
ChangeYaw( pev->yaw_speed );
|
|
|
|
if ( FacingIdeal() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_WAIT_PVS:
|
|
{
|
|
if ( !FNullEnt(FIND_CLIENT_IN_PVS(edict())) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_WAIT_INDEFINITE:
|
|
{
|
|
// don't do anything.
|
|
break;
|
|
}
|
|
case TASK_WAIT:
|
|
case TASK_WAIT_RANDOM:
|
|
{
|
|
if ( gpGlobals->time >= m_flWaitFinished )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_WAIT_FACE_ENEMY:
|
|
{
|
|
MakeIdealYaw ( m_vecEnemyLKP );
|
|
ChangeYaw( pev->yaw_speed );
|
|
|
|
if ( gpGlobals->time >= m_flWaitFinished )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_MOVE_TO_TARGET_RANGE:
|
|
{
|
|
float distance;
|
|
|
|
if ( m_hTargetEnt == NULL )
|
|
TaskFail();
|
|
else
|
|
{
|
|
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 )
|
|
m_movementActivity = ACT_WALK;
|
|
else if ( distance >= 270 && m_movementActivity != ACT_RUN )
|
|
m_movementActivity = ACT_RUN;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TASK_WAIT_FOR_MOVEMENT:
|
|
{
|
|
if (MovementIsComplete())
|
|
{
|
|
TaskComplete();
|
|
RouteClear(); // Stop moving
|
|
}
|
|
break;
|
|
}
|
|
case TASK_DIE:
|
|
{
|
|
if ( m_fSequenceFinished && pev->frame >= 255 )
|
|
{
|
|
pev->deadflag = DEAD_DEAD;
|
|
|
|
SetThink ( NULL );
|
|
StopAnimation();
|
|
|
|
if ( !BBoxFlat() )
|
|
{
|
|
// a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will
|
|
// block the player on a slope or stairs, the corpse is made nonsolid.
|
|
// pev->solid = SOLID_NOT;
|
|
UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) );
|
|
}
|
|
else // !!!HACKHACK - put monster in a thin, wide bounding box until we fix the solid type/bounding volume problem
|
|
UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + 1 ) );
|
|
|
|
if ( ShouldFadeOnDeath() )
|
|
{
|
|
// this monster was created by a monstermaker... fade the corpse out.
|
|
SUB_StartFadeOut();
|
|
}
|
|
else
|
|
{
|
|
// body is gonna be around for a while, so have it stink for a bit.
|
|
CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case TASK_RANGE_ATTACK1_NOTURN:
|
|
case TASK_MELEE_ATTACK1_NOTURN:
|
|
case TASK_MELEE_ATTACK2_NOTURN:
|
|
case TASK_RANGE_ATTACK2_NOTURN:
|
|
case TASK_RELOAD_NOTURN:
|
|
{
|
|
if ( m_fSequenceFinished )
|
|
{
|
|
m_Activity = ACT_RESET;
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_RANGE_ATTACK1:
|
|
case TASK_MELEE_ATTACK1:
|
|
case TASK_MELEE_ATTACK2:
|
|
case TASK_RANGE_ATTACK2:
|
|
case TASK_SPECIAL_ATTACK1:
|
|
case TASK_SPECIAL_ATTACK2:
|
|
case TASK_RELOAD:
|
|
{
|
|
MakeIdealYaw ( m_vecEnemyLKP );
|
|
ChangeYaw ( pev->yaw_speed );
|
|
|
|
if ( m_fSequenceFinished )
|
|
{
|
|
m_Activity = ACT_RESET;
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_SMALL_FLINCH:
|
|
{
|
|
if ( m_fSequenceFinished )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
}
|
|
break;
|
|
case TASK_WAIT_FOR_SCRIPT:
|
|
{
|
|
if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime )
|
|
{
|
|
TaskComplete();
|
|
m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszPlay, TRUE );
|
|
if ( m_fSequenceFinished )
|
|
ClearSchedule();
|
|
pev->framerate = 1.0;
|
|
//ALERT( at_aiconsole, "Script %s has begun for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) );
|
|
}
|
|
break;
|
|
}
|
|
case TASK_PLAY_SCRIPT:
|
|
{
|
|
if (m_fSequenceFinished)
|
|
{
|
|
m_pCine->SequenceDone( this );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// SetTurnActivity - measures the difference between the way
|
|
// the monster is facing and determines whether or not to
|
|
// select one of the 180 turn animations.
|
|
//=========================================================
|
|
void CBaseMonster :: SetTurnActivity ( void )
|
|
{
|
|
float flYD;
|
|
flYD = FlYawDiff();
|
|
|
|
if ( flYD <= -45 && LookupActivity ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE )
|
|
{// big right turn
|
|
m_IdealActivity = ACT_TURN_RIGHT;
|
|
}
|
|
else if ( flYD > 45 && LookupActivity ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE )
|
|
{// big left turn
|
|
m_IdealActivity = ACT_TURN_LEFT;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// Start task - selects the correct activity and performs
|
|
// any necessary calculations to start the next task on the
|
|
// schedule.
|
|
//=========================================================
|
|
void CBaseMonster :: StartTask ( Task_t *pTask )
|
|
{
|
|
switch ( pTask->iTask )
|
|
{
|
|
case TASK_TURN_RIGHT:
|
|
{
|
|
float flCurrentYaw;
|
|
|
|
flCurrentYaw = UTIL_AngleMod( pev->angles.y );
|
|
pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw - pTask->flData );
|
|
SetTurnActivity();
|
|
break;
|
|
}
|
|
case TASK_TURN_LEFT:
|
|
{
|
|
float flCurrentYaw;
|
|
|
|
flCurrentYaw = UTIL_AngleMod( pev->angles.y );
|
|
pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw + pTask->flData );
|
|
SetTurnActivity();
|
|
break;
|
|
}
|
|
case TASK_REMEMBER:
|
|
{
|
|
Remember ( (int)pTask->flData );
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_FORGET:
|
|
{
|
|
Forget ( (int)pTask->flData );
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_FIND_HINTNODE:
|
|
{
|
|
m_iHintNode = FindHintNode();
|
|
|
|
if ( m_iHintNode != NO_NODE )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_STORE_LASTPOSITION:
|
|
{
|
|
m_vecLastPosition = pev->origin;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_CLEAR_LASTPOSITION:
|
|
{
|
|
m_vecLastPosition = g_vecZero;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_CLEAR_HINTNODE:
|
|
{
|
|
m_iHintNode = NO_NODE;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_STOP_MOVING:
|
|
{
|
|
if ( m_IdealActivity == m_movementActivity )
|
|
{
|
|
m_IdealActivity = GetStoppedActivity();
|
|
}
|
|
|
|
RouteClear();
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_PLAY_SEQUENCE_FACE_ENEMY:
|
|
case TASK_PLAY_SEQUENCE_FACE_TARGET:
|
|
case TASK_PLAY_SEQUENCE:
|
|
{
|
|
m_IdealActivity = ( Activity )( int )pTask->flData;
|
|
break;
|
|
}
|
|
case TASK_PLAY_ACTIVE_IDLE:
|
|
{
|
|
// monsters verify that they have a sequence for the node's activity BEFORE
|
|
// moving towards the node, so it's ok to just set the activity without checking here.
|
|
m_IdealActivity = ( Activity )WorldGraph.m_pNodes[ m_iHintNode ].m_sHintActivity;
|
|
break;
|
|
}
|
|
case TASK_SET_SCHEDULE:
|
|
break;
|
|
case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY:
|
|
{
|
|
if ( m_hEnemy == NULL )
|
|
{
|
|
TaskFail();
|
|
return;
|
|
}
|
|
|
|
if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, pTask->flData ) )
|
|
{
|
|
// try for cover farther than the FLData from the schedule.
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no coverwhatsoever.
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY:
|
|
{
|
|
if ( m_hEnemy == NULL )
|
|
{
|
|
TaskFail();
|
|
return;
|
|
}
|
|
|
|
if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, pTask->flData, CoverRadius() ) )
|
|
{
|
|
// try for cover farther than the FLData from the schedule.
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no coverwhatsoever.
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_FIND_NODE_COVER_FROM_ENEMY:
|
|
{
|
|
if ( m_hEnemy == NULL )
|
|
{
|
|
TaskFail();
|
|
return;
|
|
}
|
|
|
|
if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, CoverRadius() ) )
|
|
{
|
|
// try for cover farther than the FLData from the schedule.
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no coverwhatsoever.
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_FIND_COVER_FROM_ENEMY:
|
|
{
|
|
entvars_t *pevCover;
|
|
|
|
if ( m_hEnemy == NULL )
|
|
{
|
|
// Find cover from self if no enemy available
|
|
pevCover = pev;
|
|
// TaskFail();
|
|
// return;
|
|
}
|
|
else
|
|
pevCover = m_hEnemy->pev;
|
|
|
|
if ( FindLateralCover( pevCover->origin, pevCover->view_ofs ) )
|
|
{
|
|
// try lateral first
|
|
m_flMoveWaitFinished = gpGlobals->time + pTask->flData;
|
|
TaskComplete();
|
|
}
|
|
else if ( FindCover( pevCover->origin, pevCover->view_ofs, 0, CoverRadius() ) )
|
|
{
|
|
// then try for plain ole cover
|
|
m_flMoveWaitFinished = gpGlobals->time + pTask->flData;
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no coverwhatsoever.
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_FIND_COVER_FROM_ORIGIN:
|
|
{
|
|
if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) )
|
|
{
|
|
// then try for plain ole cover
|
|
m_flMoveWaitFinished = gpGlobals->time + pTask->flData;
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no cover!
|
|
TaskFail();
|
|
}
|
|
}
|
|
break;
|
|
case TASK_FIND_COVER_FROM_BEST_SOUND:
|
|
{
|
|
CSound *pBestSound;
|
|
|
|
pBestSound = PBestSound();
|
|
|
|
ASSERT( pBestSound != NULL );
|
|
/*
|
|
if ( pBestSound && FindLateralCover( pBestSound->m_vecOrigin, g_vecZero ) )
|
|
{
|
|
// try lateral first
|
|
m_flMoveWaitFinished = gpGlobals->time + pTask->flData;
|
|
TaskComplete();
|
|
}
|
|
*/
|
|
|
|
if ( pBestSound && FindCover( pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius() ) )
|
|
{
|
|
// then try for plain ole cover
|
|
m_flMoveWaitFinished = gpGlobals->time + pTask->flData;
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no coverwhatsoever. or no sound in list
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_FACE_HINTNODE:
|
|
{
|
|
pev->ideal_yaw = WorldGraph.m_pNodes[ m_iHintNode ].m_flHintYaw;
|
|
SetTurnActivity();
|
|
break;
|
|
}
|
|
|
|
case TASK_FACE_LASTPOSITION:
|
|
MakeIdealYaw ( m_vecLastPosition );
|
|
SetTurnActivity();
|
|
break;
|
|
|
|
case TASK_FACE_TARGET:
|
|
if ( m_hTargetEnt != NULL )
|
|
{
|
|
MakeIdealYaw ( m_hTargetEnt->pev->origin );
|
|
SetTurnActivity();
|
|
}
|
|
else
|
|
TaskFail();
|
|
break;
|
|
case TASK_FACE_ENEMY:
|
|
{
|
|
MakeIdealYaw ( m_vecEnemyLKP );
|
|
SetTurnActivity();
|
|
break;
|
|
}
|
|
case TASK_FACE_IDEAL:
|
|
{
|
|
SetTurnActivity();
|
|
break;
|
|
}
|
|
case TASK_FACE_ROUTE:
|
|
{
|
|
if (FRouteClear())
|
|
{
|
|
ALERT(at_aiconsole, "No route to face!\n");
|
|
TaskFail();
|
|
}
|
|
else
|
|
{
|
|
MakeIdealYaw(m_Route[m_iRouteIndex].vecLocation);
|
|
SetTurnActivity();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_WAIT_PVS:
|
|
case TASK_WAIT_INDEFINITE:
|
|
{
|
|
// don't do anything.
|
|
break;
|
|
}
|
|
case TASK_WAIT:
|
|
case TASK_WAIT_FACE_ENEMY:
|
|
{// set a future time that tells us when the wait is over.
|
|
m_flWaitFinished = gpGlobals->time + pTask->flData;
|
|
break;
|
|
}
|
|
case TASK_WAIT_RANDOM:
|
|
{// set a future time that tells us when the wait is over.
|
|
m_flWaitFinished = gpGlobals->time + RANDOM_FLOAT( 0.1, pTask->flData );
|
|
break;
|
|
}
|
|
case TASK_MOVE_TO_TARGET_RANGE:
|
|
{
|
|
if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 )
|
|
TaskComplete();
|
|
else
|
|
{
|
|
m_vecMoveGoal = m_hTargetEnt->pev->origin;
|
|
if ( !MoveToTarget( ACT_WALK, 2 ) )
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_RUN_TO_TARGET:
|
|
case TASK_WALK_TO_TARGET:
|
|
{
|
|
Activity newActivity;
|
|
|
|
if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 )
|
|
TaskComplete();
|
|
else
|
|
{
|
|
if ( pTask->iTask == TASK_WALK_TO_TARGET )
|
|
newActivity = ACT_WALK;
|
|
else
|
|
newActivity = ACT_RUN;
|
|
// This monster can't do this!
|
|
if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE )
|
|
TaskComplete();
|
|
else
|
|
{
|
|
if ( m_hTargetEnt == NULL || !MoveToTarget( newActivity, 2 ) )
|
|
{
|
|
TaskFail();
|
|
ALERT( at_aiconsole, "%s Failed to reach target!!!\n", STRING(pev->classname) );
|
|
RouteClear();
|
|
}
|
|
}
|
|
}
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_CLEAR_MOVE_WAIT:
|
|
{
|
|
m_flMoveWaitFinished = gpGlobals->time;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_MELEE_ATTACK1_NOTURN:
|
|
case TASK_MELEE_ATTACK1:
|
|
{
|
|
m_IdealActivity = ACT_MELEE_ATTACK1;
|
|
break;
|
|
}
|
|
case TASK_MELEE_ATTACK2_NOTURN:
|
|
case TASK_MELEE_ATTACK2:
|
|
{
|
|
m_IdealActivity = ACT_MELEE_ATTACK2;
|
|
break;
|
|
}
|
|
case TASK_RANGE_ATTACK1_NOTURN:
|
|
case TASK_RANGE_ATTACK1:
|
|
{
|
|
m_IdealActivity = ACT_RANGE_ATTACK1;
|
|
break;
|
|
}
|
|
case TASK_RANGE_ATTACK2_NOTURN:
|
|
case TASK_RANGE_ATTACK2:
|
|
{
|
|
m_IdealActivity = ACT_RANGE_ATTACK2;
|
|
break;
|
|
}
|
|
case TASK_RELOAD_NOTURN:
|
|
case TASK_RELOAD:
|
|
{
|
|
m_IdealActivity = ACT_RELOAD;
|
|
break;
|
|
}
|
|
case TASK_SPECIAL_ATTACK1:
|
|
{
|
|
m_IdealActivity = ACT_SPECIAL_ATTACK1;
|
|
break;
|
|
}
|
|
case TASK_SPECIAL_ATTACK2:
|
|
{
|
|
m_IdealActivity = ACT_SPECIAL_ATTACK2;
|
|
break;
|
|
}
|
|
case TASK_SET_ACTIVITY:
|
|
{
|
|
m_IdealActivity = (Activity)(int)pTask->flData;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_GET_PATH_TO_ENEMY_LKP:
|
|
{
|
|
if ( BuildRoute ( m_vecEnemyLKP, bits_MF_TO_LOCATION, NULL ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, 0, (m_vecEnemyLKP - pev->origin).Length() ))
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" );
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_GET_PATH_TO_ENEMY:
|
|
{
|
|
CBaseEntity *pEnemy = m_hEnemy;
|
|
|
|
if ( pEnemy == NULL )
|
|
{
|
|
TaskFail();
|
|
return;
|
|
}
|
|
|
|
if ( BuildRoute ( pEnemy->pev->origin, bits_MF_TO_ENEMY, pEnemy ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, 0, (pEnemy->pev->origin - pev->origin).Length() ))
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" );
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_GET_PATH_TO_ENEMY_CORPSE:
|
|
{
|
|
UTIL_MakeVectors( pev->angles );
|
|
if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 64, bits_MF_TO_LOCATION, NULL ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
ALERT ( at_aiconsole, "GetPathToEnemyCorpse failed!!\n" );
|
|
TaskFail();
|
|
}
|
|
}
|
|
break;
|
|
case TASK_GET_PATH_TO_SPOT:
|
|
{
|
|
CBaseEntity *pPlayer = CBaseEntity::Instance( FIND_ENTITY_BY_CLASSNAME( NULL, "player" ) );
|
|
if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, pPlayer ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" );
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TASK_GET_PATH_TO_TARGET:
|
|
{
|
|
RouteClear();
|
|
if ( m_hTargetEnt != NULL && MoveToTarget( m_movementActivity, 1 ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" );
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_GET_PATH_TO_HINTNODE:// for active idles!
|
|
{
|
|
if ( MoveToLocation( m_movementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
ALERT ( at_aiconsole, "GetPathToHintNode failed!!\n" );
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_GET_PATH_TO_LASTPOSITION:
|
|
{
|
|
m_vecMoveGoal = m_vecLastPosition;
|
|
|
|
if ( MoveToLocation( m_movementActivity, 2, m_vecMoveGoal ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
ALERT ( at_aiconsole, "GetPathToLastPosition failed!!\n" );
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_GET_PATH_TO_BESTSOUND:
|
|
{
|
|
CSound *pSound;
|
|
|
|
pSound = PBestSound();
|
|
|
|
if ( pSound && MoveToLocation( m_movementActivity, 2, pSound->m_vecOrigin ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" );
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_GET_PATH_TO_BESTSCENT:
|
|
{
|
|
CSound *pScent;
|
|
|
|
pScent = PBestScent();
|
|
|
|
if ( pScent && MoveToLocation( m_movementActivity, 2, pScent->m_vecOrigin ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
ALERT ( at_aiconsole, "GetPathToBestScent failed!!\n" );
|
|
|
|
TaskFail();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_RUN_PATH:
|
|
{
|
|
// UNDONE: This is in some default AI and some monsters can't run? -- walk instead?
|
|
if ( LookupActivity( ACT_RUN ) != ACTIVITY_NOT_AVAILABLE )
|
|
{
|
|
m_movementActivity = ACT_RUN;
|
|
}
|
|
else
|
|
{
|
|
m_movementActivity = ACT_WALK;
|
|
}
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_WALK_PATH:
|
|
{
|
|
if ( pev->movetype == MOVETYPE_FLY )
|
|
{
|
|
m_movementActivity = ACT_FLY;
|
|
}
|
|
if ( LookupActivity( ACT_WALK ) != ACTIVITY_NOT_AVAILABLE )
|
|
{
|
|
m_movementActivity = ACT_WALK;
|
|
}
|
|
else
|
|
{
|
|
m_movementActivity = ACT_RUN;
|
|
}
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_STRAFE_PATH:
|
|
{
|
|
Vector2D vec2DirToPoint;
|
|
Vector2D vec2RightSide;
|
|
|
|
// to start strafing, we have to first figure out if the target is on the left side or right side
|
|
UTIL_MakeVectors ( pev->angles );
|
|
|
|
vec2DirToPoint = ( m_Route[ 0 ].vecLocation - pev->origin ).Make2D().Normalize();
|
|
vec2RightSide = gpGlobals->v_right.Make2D().Normalize();
|
|
|
|
if ( DotProduct ( vec2DirToPoint, vec2RightSide ) > 0 )
|
|
{
|
|
// strafe right
|
|
m_movementActivity = ACT_STRAFE_RIGHT;
|
|
}
|
|
else
|
|
{
|
|
// strafe left
|
|
m_movementActivity = ACT_STRAFE_LEFT;
|
|
}
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
|
|
|
|
case TASK_WAIT_FOR_MOVEMENT:
|
|
{
|
|
if (FRouteClear())
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TASK_EAT:
|
|
{
|
|
Eat( pTask->flData );
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_SMALL_FLINCH:
|
|
{
|
|
m_IdealActivity = GetSmallFlinchActivity();
|
|
break;
|
|
}
|
|
case TASK_DIE:
|
|
{
|
|
RouteClear();
|
|
|
|
m_IdealActivity = GetDeathActivity();
|
|
|
|
pev->deadflag = DEAD_DYING;
|
|
break;
|
|
}
|
|
case TASK_SOUND_WAKE:
|
|
{
|
|
AlertSound();
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_SOUND_DIE:
|
|
{
|
|
DeathSound();
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_SOUND_IDLE:
|
|
{
|
|
IdleSound();
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_SOUND_PAIN:
|
|
{
|
|
PainSound();
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_SOUND_DEATH:
|
|
{
|
|
DeathSound();
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_SOUND_ANGRY:
|
|
{
|
|
// sounds are complete as soon as we get here, cause we've already played them.
|
|
ALERT ( at_aiconsole, "SOUND\n" );
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_WAIT_FOR_SCRIPT:
|
|
{
|
|
if (m_pCine->m_iszIdle)
|
|
{
|
|
m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszIdle, FALSE );
|
|
if (FStrEq( STRING(m_pCine->m_iszIdle), STRING(m_pCine->m_iszPlay)))
|
|
{
|
|
pev->framerate = 0;
|
|
}
|
|
}
|
|
else
|
|
m_IdealActivity = ACT_IDLE;
|
|
|
|
break;
|
|
}
|
|
case TASK_PLAY_SCRIPT:
|
|
{
|
|
pev->movetype = MOVETYPE_FLY;
|
|
ClearBits(pev->flags, FL_ONGROUND);
|
|
m_scriptState = SCRIPT_PLAYING;
|
|
break;
|
|
}
|
|
case TASK_ENABLE_SCRIPT:
|
|
{
|
|
m_pCine->DelayStart( 0 );
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_PLANT_ON_SCRIPT:
|
|
{
|
|
if ( m_hTargetEnt != NULL )
|
|
{
|
|
pev->origin = m_hTargetEnt->pev->origin; // Plant on target
|
|
}
|
|
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_FACE_SCRIPT:
|
|
{
|
|
if ( m_hTargetEnt != NULL )
|
|
{
|
|
pev->ideal_yaw = UTIL_AngleMod( m_hTargetEnt->pev->angles.y );
|
|
}
|
|
|
|
TaskComplete();
|
|
m_IdealActivity = ACT_IDLE;
|
|
RouteClear();
|
|
break;
|
|
}
|
|
|
|
case TASK_SUGGEST_STATE:
|
|
{
|
|
m_IdealMonsterState = (MONSTERSTATE)(int)pTask->flData;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
|
|
case TASK_SET_FAIL_SCHEDULE:
|
|
m_failSchedule = (int)pTask->flData;
|
|
TaskComplete();
|
|
break;
|
|
|
|
case TASK_CLEAR_FAIL_SCHEDULE:
|
|
m_failSchedule = SCHED_NONE;
|
|
TaskComplete();
|
|
break;
|
|
|
|
default:
|
|
{
|
|
ALERT ( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// GetTask - returns a pointer to the current
|
|
// scheduled task. NULL if there's a problem.
|
|
//=========================================================
|
|
Task_t *CBaseMonster :: GetTask ( void )
|
|
{
|
|
if ( m_iScheduleIndex < 0 || m_iScheduleIndex >= m_pSchedule->cTasks )
|
|
{
|
|
// m_iScheduleIndex is not within valid range for the monster's current schedule.
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return &m_pSchedule->pTasklist[ m_iScheduleIndex ];
|
|
}
|
|
}
|
|
|