halflife-photomode/dlls/schedule.cpp

1511 lines
32 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 "defaultai.h"
#include "soundent.h"
//=========================================================
// FHaveSchedule - Returns true if monster's m_pSchedule
// is anything other than NULL.
//=========================================================
bool CBaseMonster::FHaveSchedule()
{
if (m_pSchedule == NULL)
{
return false;
}
return true;
}
//=========================================================
// ClearSchedule - blanks out the caller's schedule pointer
// and index.
//=========================================================
void CBaseMonster::ClearSchedule()
{
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()
{
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) != 0 && (m_pSchedule->iSoundMask) == 0)
{
ALERT(at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n");
}
else if (0 != m_pSchedule->iSoundMask && (m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) == 0)
{
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()
{
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()
{
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()
{
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()
{
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 ((0 != m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) ||
(m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE) != 0) ||
((m_MonsterState == MONSTERSTATE_COMBAT) && (m_hEnemy == NULL)))
{
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 == 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()
{
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 == 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 (!FStringNull(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(false);
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()
{
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()
{
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];
}