2
0
mirror of https://github.com/FWGS/hlsdk-xash3d synced 2024-11-11 12:49:49 +01:00
hlsdk-xash3d/dlls/schedule.cpp

1618 lines
37 KiB
C++

/***
*
* 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.
*
****/
//=========================================================
// 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 "defaultai.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" );
}
#if _DEBUG
if( !ScheduleFromName( pNewSchedule->pName ) )
{
ALERT( at_console, "Schedule %s not in table!!!\n", pNewSchedule->pName );
}
#endif
// 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 )
{
Schedule_t *pNewSchedule;
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();
// Call GetIdealState if we're not dead and one or more of the following...
// - in COMBAT state with no enemy (it died?)
// - conditions bits (excluding SCHEDULE_DONE) indicate interruption,
// - schedule is done but schedule indicates it wants GetIdealState called
// after successful completion (by setting bits_COND_SCHEDULE_DONE in iInterruptMask)
// DEAD & SCRIPT are not suggestions, they are commands!
if( m_IdealMonsterState != MONSTERSTATE_DEAD &&
( m_IdealMonsterState != MONSTERSTATE_SCRIPT || m_IdealMonsterState == m_MonsterState ) )
{
// if we're here, then either we're being told to do something (besides dying or playing a script)
// or our current schedule (besides dying) is invalid. -- LRC
if( (m_afConditions && !HasConditions( bits_COND_SCHEDULE_DONE ) ) ||
( m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE ) ) ||
( ( m_MonsterState == MONSTERSTATE_COMBAT ) && ( m_hEnemy == 0 ) ) )
{
GetIdealState();
}
}
if( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState )
{
if( m_failSchedule != SCHED_NONE )
pNewSchedule = GetScheduleOfType( m_failSchedule );
else
pNewSchedule = GetScheduleOfType( SCHED_FAIL );
// schedule was invalid because the current task failed to start or complete
ALERT( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex );
ChangeSchedule( pNewSchedule );
}
else
{
SetState( m_IdealMonsterState );
if( m_MonsterState == MONSTERSTATE_SCRIPT || m_MonsterState == MONSTERSTATE_DEAD )
pNewSchedule = CBaseMonster::GetSchedule();
else
pNewSchedule = GetSchedule();
ChangeSchedule( pNewSchedule );
}
}
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 == 0 )
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();
}
break;
}
case TASK_PLAY_SCRIPT:
{
if( m_fSequenceFinished )
{
ALERT( at_console, "Anim Finished\n" );
if( m_pCine->m_iRepeatsLeft > 0 )
{
//ALERT( at_console, "Frame %f; Repeat %d from %f\n", pev->frame, m_pCine->m_iRepeatsLeft, m_pCine->m_fRepeatFrame );
m_pCine->m_iRepeatsLeft--;
pev->frame = m_pCine->m_fRepeatFrame;
ResetSequenceInfo();
}
else
{
TaskComplete();
}
}
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:
{
Schedule_t *pNewSchedule;
pNewSchedule = GetScheduleOfType( (int)pTask->flData );
if( pNewSchedule )
{
ChangeSchedule( pNewSchedule );
}
else
{
TaskFail();
}
break;
}
case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY:
{
if( m_hEnemy == 0 )
{
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 == 0 )
{
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 == 0 )
{
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 == 0 )
{
// 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 != 0 )
{
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_SCRIPT:
case TASK_WALK_TO_SCRIPT:
{
Activity newActivity;
if( !m_pGoalEnt || ( m_pGoalEnt->pev->origin - pev->origin ).Length() < 1 )
TaskComplete();
else
{
if( pTask->iTask == TASK_WALK_TO_SCRIPT )
newActivity = ACT_WALK;
else
newActivity = ACT_RUN;
// This monster can't do this!
if( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE )
TaskComplete();
else
{
if( m_pGoalEnt != NULL )
{
Vector vecDest;
vecDest = m_pGoalEnt->pev->origin;
if( !MoveToLocation( newActivity, 2, vecDest ) )
{
TaskFail();
ALERT( at_aiconsole, "%s Failed to reach script!!!\n", STRING( pev->classname ) );
RouteClear();
}
}
else
{
TaskFail();
ALERT( at_aiconsole, "%s: MoveTarget is missing!?!\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 = UTIL_FindEntityByClassname( 0, "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 != 0 && MoveToTarget( m_movementActivity, 1 ) )
{
TaskComplete();
}
else
{
// no way to get there =(
ALERT( at_aiconsole, "GetPathToSpot failed!!\n" );
TaskFail();
}
break;
}
case TASK_GET_PATH_TO_SCRIPT:
{
RouteClear();
if( m_pCine != 0 && MoveToLocation( m_movementActivity, 1, m_pCine->pev->origin ) )
{
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_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime )
{
TaskComplete(); //LRC - start playing immediately
}
else if( !m_pCine->IsAction() && 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:
{
if( m_pCine->IsAction() )
{
// ALERT( at_console, "PlayScript: setting idealactivity %d\n",m_pCine->m_fAction );
switch( m_pCine->m_fAction )
{
case 0:
m_IdealActivity = ACT_RANGE_ATTACK1;
break;
case 1:
m_IdealActivity = ACT_RANGE_ATTACK2;
break;
case 2:
m_IdealActivity = ACT_MELEE_ATTACK1;
break;
case 3:
m_IdealActivity = ACT_MELEE_ATTACK2;
break;
case 4:
m_IdealActivity = ACT_SPECIAL_ATTACK1;
break;
case 5:
m_IdealActivity = ACT_SPECIAL_ATTACK2;
break;
case 6:
m_IdealActivity = ACT_RELOAD;
break;
case 7:
m_IdealActivity = ACT_HOP;
break;
}
pev->framerate = 1.0; // shouldn't be needed, but just in case
pev->movetype = MOVETYPE_FLY;
ClearBits( pev->flags, FL_ONGROUND );
}
else
{
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 ) );
}
m_scriptState = SCRIPT_PLAYING;
break;
}
//LRC
case TASK_END_SCRIPT:
{
m_pCine->SequenceDone( this );
TaskComplete();
break;
}
case TASK_ENABLE_SCRIPT:
{
m_pCine->DelayStart( 0 );
TaskComplete();
break;
}
case TASK_PLANT_ON_SCRIPT:
{
if ( m_pCine != NULL )
{
// Plant on script
// LRC - if it's a teleport script, do the turn too
if (m_pCine->m_fMoveTo == 4 || m_pCine->m_fMoveTo == 6)
{
if (m_pCine->m_fTurnType == 0) //LRC
pev->angles.y = m_hTargetEnt->pev->angles.y;
else if (m_pCine->m_fTurnType == 1)
pev->angles.y = UTIL_VecToYaw(m_hTargetEnt->pev->origin - pev->origin);
pev->ideal_yaw = pev->angles.y;
pev->avelocity = Vector( 0, 0, 0 );
pev->velocity = Vector( 0, 0, 0 );
pev->effects |= EF_NOINTERP;
}
if (m_pCine->m_fMoveTo != 6)
pev->origin = m_pGoalEnt->pev->origin;
}
TaskComplete();
break;
}
case TASK_FACE_SCRIPT:
{
if ( m_pCine != NULL && m_pCine->m_fMoveTo != 0) // movetype "no move" makes us ignore turntype
{
switch (m_pCine->m_fTurnType)
{
case 0:
pev->ideal_yaw = UTIL_AngleMod( m_pCine->pev->angles.y );
break;
case 1:
// yes, this is inconsistent- turn to face uses the "target" and turn to angle uses the "cine".
if (m_hTargetEnt)
MakeIdealYaw ( m_hTargetEnt->pev->origin );
else
MakeIdealYaw ( m_pCine->pev->origin );
break;
// default: don't turn
}
}
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];
}
}
//=========================================================
// GetSchedule - Decides which type of schedule best suits
// the monster's current state and conditions. Then calls
// monster's member function to get a pointer to a schedule
// of the proper type.
//=========================================================
Schedule_t *CBaseMonster::GetSchedule( void )
{
switch( m_MonsterState )
{
case MONSTERSTATE_PRONE:
{
return GetScheduleOfType( SCHED_BARNACLE_VICTIM_GRAB );
break;
}
case MONSTERSTATE_NONE:
{
ALERT( at_aiconsole, "MONSTERSTATE IS NONE!\n" );
break;
}
case MONSTERSTATE_IDLE:
{
if( HasConditions( bits_COND_HEAR_SOUND ) )
{
return GetScheduleOfType( SCHED_ALERT_FACE );
}
else if( FRouteClear() )
{
// no valid route!
return GetScheduleOfType( SCHED_IDLE_STAND );
}
else
{
// valid route. Get moving
return GetScheduleOfType( SCHED_IDLE_WALK );
}
break;
}
case MONSTERSTATE_ALERT:
{
if( HasConditions( bits_COND_ENEMY_DEAD ) && LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE )
{
return GetScheduleOfType( SCHED_VICTORY_DANCE );
}
if( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) )
{
if( fabs( FlYawDiff() ) < ( 1.0 - m_flFieldOfView ) * 60 ) // roughly in the correct direction
{
return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ORIGIN );
}
else
{
return GetScheduleOfType( SCHED_ALERT_SMALL_FLINCH );
}
}
else if( HasConditions ( bits_COND_HEAR_SOUND ) )
{
return GetScheduleOfType( SCHED_ALERT_FACE );
}
else
{
return GetScheduleOfType( SCHED_ALERT_STAND );
}
break;
}
case MONSTERSTATE_COMBAT:
{
if( HasConditions( bits_COND_ENEMY_DEAD ) )
{
// clear the current (dead) enemy and try to find another.
m_hEnemy = NULL;
if( GetEnemy() )
{
ClearConditions( bits_COND_ENEMY_DEAD );
return GetSchedule();
}
else
{
SetState( MONSTERSTATE_ALERT );
return GetSchedule();
}
}
if( HasConditions( bits_COND_NEW_ENEMY ) )
{
return GetScheduleOfType( SCHED_WAKE_ANGRY );
}
else if( HasConditions( bits_COND_LIGHT_DAMAGE ) && !HasMemory( bits_MEMORY_FLINCHED ) )
{
return GetScheduleOfType( SCHED_SMALL_FLINCH );
}
else if( !HasConditions( bits_COND_SEE_ENEMY ) )
{
// we can't see the enemy
if( !HasConditions( bits_COND_ENEMY_OCCLUDED ) )
{
// enemy is unseen, but not occluded!
// turn to face enemy
return GetScheduleOfType( SCHED_COMBAT_FACE );
}
else
{
// chase!
return GetScheduleOfType( SCHED_CHASE_ENEMY );
}
}
else
{
// we can see the enemy
if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) )
{
return GetScheduleOfType( SCHED_RANGE_ATTACK1 );
}
if( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) )
{
return GetScheduleOfType( SCHED_RANGE_ATTACK2 );
}
if( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) )
{
return GetScheduleOfType( SCHED_MELEE_ATTACK1 );
}
if( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) )
{
return GetScheduleOfType( SCHED_MELEE_ATTACK2 );
}
if( !HasConditions( bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 ) )
{
// if we can see enemy but can't use either attack type, we must need to get closer to enemy
return GetScheduleOfType( SCHED_CHASE_ENEMY );
}
else if( !FacingIdeal() )
{
//turn
return GetScheduleOfType( SCHED_COMBAT_FACE );
}
else
{
ALERT( at_aiconsole, "No suitable combat schedule!\n" );
}
}
break;
}
case MONSTERSTATE_DEAD:
{
return GetScheduleOfType( SCHED_DIE );
break;
}
case MONSTERSTATE_SCRIPT:
{
ASSERT( m_pCine != NULL );
if( !m_pCine )
{
ALERT( at_aiconsole, "Script failed for %s\n", STRING( pev->classname ) );
CineCleanup();
return GetScheduleOfType( SCHED_IDLE_STAND );
}
return GetScheduleOfType( SCHED_AISCRIPT );
}
default:
{
ALERT( at_aiconsole, "Invalid State for GetSchedule!\n" );
break;
}
}
return &slError[0];
}