//========================================================= // 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]; }
//========================================================= // 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 ( (m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) || (m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE)) || ((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 ); } }
//========================================================= // 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; // modif de Julien case TASK_GET_BURNT_COVER: { int ifail = 0; // plusieurs essais for ( int boucle = 0; boucle < 10; boucle ++ ) { // coordonnée aléatoire Vector vecDest = pev->origin; vecDest.x += RANDOM_FLOAT ( -256, 256 ); vecDest.y += RANDOM_FLOAT ( -256, 256 ); if ( !MoveToLocation( ACT_RUN, 0.1, vecDest ) == TRUE ) { ifail = 1; } else { ifail = 0; break; } } // bilan if ( ifail == 1 ) { TaskFail(); } else { TaskComplete(); } } 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; } } }
//========================================================= // 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 *CHAssassin :: GetSchedule ( void ) { switch ( m_MonsterState ) { case MONSTERSTATE_IDLE: case MONSTERSTATE_ALERT: { if ( HasConditions ( bits_COND_HEAR_SOUND )) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) { return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); } if ( pSound && (pSound->m_iType & bits_SOUND_COMBAT) ) { return GetScheduleOfType( SCHED_INVESTIGATE_SOUND ); } } } break; case MONSTERSTATE_COMBAT: { // dead enemy if ( HasConditions( bits_COND_ENEMY_DEAD ) ) { // call base class, all code to handle dead enemies is centralized there. return CBaseMonster :: GetSchedule(); } // flying? if ( pev->movetype == MOVETYPE_TOSS) { if (pev->flags & FL_ONGROUND) { // ALERT( at_console, "landed\n"); // just landed pev->movetype = MOVETYPE_STEP; return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_LAND ); } else { // ALERT( at_console, "jump\n"); // jump or jump/shoot if ( m_MonsterState == MONSTERSTATE_COMBAT ) return GetScheduleOfType ( SCHED_ASSASSIN_JUMP ); else return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_ATTACK ); } } if ( HasConditions ( bits_COND_HEAR_SOUND )) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) { return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); } } if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) { m_iFrustration++; } if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) { m_iFrustration++; } // jump player! if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) { // ALERT( at_console, "melee attack 1\n"); return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); } // throw grenade if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) ) { // ALERT( at_console, "range attack 2\n"); return GetScheduleOfType ( SCHED_RANGE_ATTACK2 ); } // spotted if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) { // ALERT( at_console, "exposed\n"); m_iFrustration++; return GetScheduleOfType ( SCHED_ASSASSIN_EXPOSED ); } // can attack if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) { // ALERT( at_console, "range attack 1\n"); m_iFrustration = 0; return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); } if ( HasConditions ( bits_COND_SEE_ENEMY ) ) { // ALERT( at_console, "face\n"); return GetScheduleOfType ( SCHED_COMBAT_FACE ); } // new enemy if ( HasConditions ( bits_COND_NEW_ENEMY ) ) { // ALERT( at_console, "take cover\n"); return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); } // ALERT( at_console, "stand\n"); return GetScheduleOfType ( SCHED_ALERT_STAND ); } break; } return CBaseMonster :: GetSchedule(); }
//========================================================= // GetScheduleOfType - returns a pointer to one of the // monster's available schedules of the indicated type. //========================================================= Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) { // ALERT ( at_console, "Sched Type:%d\n", Type ); switch ( Type ) { // This is the schedule for scripted sequences AND scripted AI case SCHED_AISCRIPT: { ASSERT( m_pCine != NULL ); if ( !m_pCine ) { ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); CineCleanup(); return GetScheduleOfType( SCHED_IDLE_STAND ); } // else // ALERT( at_aiconsole, "Starting script %s for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); switch ( m_pCine->m_fMoveTo ) { case 0: case 4: return slWaitScript; case 1: return slWalkToScript; case 2: return slRunToScript; case 5: return slFaceScript; } break; } case SCHED_IDLE_STAND: { if ( RANDOM_LONG(0,14) == 0 && FCanActiveIdle() ) { return &slActiveIdle[ 0 ]; } return &slIdleStand[ 0 ]; } case SCHED_IDLE_WALK: { return &slIdleWalk[ 0 ]; } case SCHED_WAIT_TRIGGER: { return &slIdleTrigger[ 0 ]; } case SCHED_WAKE_ANGRY: { return &slWakeAngry[ 0 ]; } case SCHED_ALERT_FACE: { return &slAlertFace[ 0 ]; } case SCHED_ALERT_STAND: { return &slAlertStand[ 0 ]; } case SCHED_COMBAT_STAND: { return &slCombatStand[ 0 ]; } case SCHED_COMBAT_FACE: { return &slCombatFace[ 0 ]; } case SCHED_CHASE_ENEMY: { return &slChaseEnemy[ 0 ]; } case SCHED_CHASE_ENEMY_FAILED: { return &slFail[ 0 ]; } case SCHED_SMALL_FLINCH: { return &slSmallFlinch[ 0 ]; } case SCHED_ALERT_SMALL_FLINCH: { return &slAlertSmallFlinch[ 0 ]; } case SCHED_RELOAD: { return &slReload[ 0 ]; } case SCHED_ARM_WEAPON: { return &slArmWeapon[ 0 ]; } case SCHED_STANDOFF: { return &slStandoff[ 0 ]; } case SCHED_RANGE_ATTACK1: { return &slRangeAttack1[ 0 ]; } case SCHED_RANGE_ATTACK2: { return &slRangeAttack2[ 0 ]; } case SCHED_MELEE_ATTACK1: { return &slPrimaryMeleeAttack[ 0 ]; } case SCHED_MELEE_ATTACK2: { return &slSecondaryMeleeAttack[ 0 ]; } case SCHED_SPECIAL_ATTACK1: { return &slSpecialAttack1[ 0 ]; } case SCHED_SPECIAL_ATTACK2: { return &slSpecialAttack2[ 0 ]; } case SCHED_TAKE_COVER_FROM_BEST_SOUND: { return &slTakeCoverFromBestSound[ 0 ]; } case SCHED_TAKE_COVER_FROM_ENEMY: { return &slTakeCoverFromEnemy[ 0 ]; } case SCHED_COWER: { return &slCower[ 0 ]; } case SCHED_AMBUSH: { return &slAmbush[ 0 ]; } case SCHED_BARNACLE_VICTIM_GRAB: { return &slBarnacleVictimGrab[ 0 ]; } case SCHED_BARNACLE_VICTIM_CHOMP: { return &slBarnacleVictimChomp[ 0 ]; } case SCHED_INVESTIGATE_SOUND: { return &slInvestigateSound[ 0 ]; } case SCHED_DIE: { return &slDie[ 0 ]; } case SCHED_TAKE_COVER_FROM_ORIGIN: { return &slTakeCoverFromOrigin[ 0 ]; } case SCHED_VICTORY_DANCE: { return &slVictoryDance[ 0 ]; } case SCHED_FAIL: { return slFail; } default: { ALERT ( at_console, "GetScheduleOfType()\nNo CASE for Schedule Type %d!\n", Type ); return &slIdleStand[ 0 ]; break; } } return NULL; }
Schedule_t *CScientist :: GetSchedule ( void ) { // so we don't keep calling through the EHANDLE stuff CBaseEntity *pEnemy = m_hEnemy; if ( HasConditions( bits_COND_HEAR_SOUND ) ) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); } switch( m_MonsterState ) { case MONSTERSTATE_ALERT: case MONSTERSTATE_IDLE: if ( pEnemy ) { if ( HasConditions( bits_COND_SEE_ENEMY ) ) m_fearTime = gpGlobals->time; else if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert { m_hEnemy = NULL; pEnemy = NULL; } } if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) { // flinch if hurt return GetScheduleOfType( SCHED_SMALL_FLINCH ); } // Cower when you hear something scary if ( HasConditions( bits_COND_HEAR_SOUND ) ) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if ( pSound ) { if ( pSound->m_iType & (bits_SOUND_DANGER | bits_SOUND_COMBAT) ) { if ( gpGlobals->time - m_fearTime > 3 ) // Only cower every 3 seconds or so { m_fearTime = gpGlobals->time; // Update last fear return GetScheduleOfType( SCHED_STARTLE ); // This will just duck for a second } } } } // Behavior for following the player if ( IsFollowing() ) { if ( !m_hTargetEnt->IsAlive() ) { // UNDONE: Comment about the recently dead player here? StopFollowing( FALSE ); break; } int relationship = R_NO; // Nothing scary, just me and the player if ( pEnemy != NULL ) relationship = IRelationship( pEnemy ); // UNDONE: Model fear properly, fix R_FR and add multiple levels of fear if ( relationship != R_DL && relationship != R_HT ) { // If I'm already close enough to my target if ( TargetDistance() <= 128 ) { if ( CanHeal() ) // Heal opportunistically return slHeal; if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); } return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow. } else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared { if ( HasConditions( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react return GetScheduleOfType( SCHED_FEAR ); // React to something scary return GetScheduleOfType( SCHED_TARGET_FACE_SCARED ); // face and follow, but I'm scared! } } if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move return GetScheduleOfType( SCHED_MOVE_AWAY ); // try to say something about smells TrySmellTalk(); break; case MONSTERSTATE_COMBAT: if ( HasConditions( bits_COND_NEW_ENEMY ) ) return slFear; // Point and scream! if ( HasConditions( bits_COND_SEE_ENEMY ) ) return slScientistCover; // Take Cover if ( HasConditions( bits_COND_HEAR_SOUND ) ) return slTakeCoverFromBestSound; // Cower and panic from the scary sound! return slScientistCover; // Run & Cower break; } return CTalkMonster::GetSchedule(); }
//========================================================= // 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 *CAGrunt :: GetSchedule ( void ) { if ( HasConditions(bits_COND_HEAR_SOUND) ) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) { // dangerous sound nearby! return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); } } switch ( m_MonsterState ) { case MONSTERSTATE_COMBAT: { // dead enemy if ( HasConditions( bits_COND_ENEMY_DEAD ) ) { // call base class, all code to handle dead enemies is centralized there. return CBaseMonster :: GetSchedule(); } if ( HasConditions(bits_COND_NEW_ENEMY) ) { return GetScheduleOfType( SCHED_WAKE_ANGRY ); } // zap player! if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) { AttackSound();// this is a total hack. Should be parto f the schedule return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); } if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) { return GetScheduleOfType( SCHED_SMALL_FLINCH ); } // can attack if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot ( bits_SLOTS_AGRUNT_HORNET ) ) { return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); } if ( OccupySlot ( bits_SLOT_AGRUNT_CHASE ) ) { return GetScheduleOfType ( SCHED_CHASE_ENEMY ); } return GetScheduleOfType ( SCHED_STANDOFF ); } default: break; } return CSquadMonster :: GetSchedule(); }
//========================================================= // GetSchedule //========================================================= Schedule_t *CBullsquid :: GetSchedule( void ) { switch ( m_MonsterState ) { case MONSTERSTATE_ALERT: { if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) { return GetScheduleOfType ( SCHED_SQUID_HURTHOP ); } if ( HasConditions(bits_COND_SMELL_FOOD) ) { CSound *pSound; pSound = PBestScent(); if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) { // scent is behind or occluded return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); } // food is right out in the open. Just go get it. return GetScheduleOfType( SCHED_SQUID_EAT ); } if ( HasConditions(bits_COND_SMELL) ) { // there's something stinky. CSound *pSound; pSound = PBestScent(); if ( pSound ) return GetScheduleOfType( SCHED_SQUID_WALLOW); } break; } case MONSTERSTATE_COMBAT: { // dead enemy if ( HasConditions( bits_COND_ENEMY_DEAD ) ) { // call base class, all code to handle dead enemies is centralized there. return CBaseMonster :: GetSchedule(); } if ( HasConditions(bits_COND_NEW_ENEMY) ) { if ( m_fCanThreatDisplay && IRelationship( m_hEnemy ) == R_HT ) { // this means squid sees a headcrab! m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime. return GetScheduleOfType ( SCHED_SQUID_SEECRAB ); } else { return GetScheduleOfType ( SCHED_WAKE_ANGRY ); } } if ( HasConditions(bits_COND_SMELL_FOOD) ) { CSound *pSound; pSound = PBestScent(); if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) { // scent is behind or occluded return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); } // food is right out in the open. Just go get it. return GetScheduleOfType( SCHED_SQUID_EAT ); } if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); } if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); } if ( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) ) { return GetScheduleOfType ( SCHED_MELEE_ATTACK2 ); } return GetScheduleOfType ( SCHED_CHASE_ENEMY ); break; } } return CBaseMonster :: GetSchedule(); }
//========================================================= // 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 *CBarney :: GetSchedule ( void ) { if ( HasConditions( bits_COND_HEAR_SOUND ) ) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); } if ( HasConditions( bits_COND_ENEMY_DEAD ) && FOkToSpeak() ) { // Hey, be careful with that if (m_iszSpeakAs) { char szBuf[32]; strcpy(szBuf,STRING(m_iszSpeakAs)); strcat(szBuf,"_KILL"); PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); } else { PlaySentence( "BA_KILL", 4, VOL_NORM, ATTN_NORM ); } } switch( m_MonsterState ) { case MONSTERSTATE_COMBAT: { // dead enemy if ( HasConditions( bits_COND_ENEMY_DEAD ) ) { // call base class, all code to handle dead enemies is centralized there. return CBaseMonster :: GetSchedule(); } // always act surprized with a new enemy if ( HasConditions( bits_COND_NEW_ENEMY ) && HasConditions( bits_COND_LIGHT_DAMAGE) ) return GetScheduleOfType( SCHED_SMALL_FLINCH ); // wait for one schedule to draw gun if (!m_fGunDrawn ) return GetScheduleOfType( SCHED_ARM_WEAPON ); if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); } break; case MONSTERSTATE_ALERT: case MONSTERSTATE_IDLE: if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) { // flinch if hurt return GetScheduleOfType( SCHED_SMALL_FLINCH ); } if ( m_hEnemy == NULL && IsFollowing() ) { if ( !m_hTargetEnt->IsAlive() ) { // UNDONE: Comment about the recently dead player here? StopFollowing( FALSE ); break; } else { if ( HasConditions( bits_COND_CLIENT_PUSH ) ) { return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); } return GetScheduleOfType( SCHED_TARGET_FACE ); } } if ( HasConditions( bits_COND_CLIENT_PUSH ) ) { return GetScheduleOfType( SCHED_MOVE_AWAY ); } // try to say something about smells TrySmellTalk(); break; } return CTalkMonster::GetSchedule(); }
//========================================================= // 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 *CFriend :: GetSchedule ( void ) { if ( HasConditions( bits_COND_HEAR_SOUND ) ) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); } if ( HasConditions( bits_COND_ENEMY_DEAD ) && FOkToSpeak() ) { // Hey, be careful with that if (m_iszSpeakAs) { char szBuf[32]; strcpy(szBuf,STRING(m_iszSpeakAs)); strcat(szBuf,"_KILL"); PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); } else { PlaySentence( "FG_KILL", 4, VOL_NORM, ATTN_NORM ); } } switch( m_MonsterState ) { case MONSTERSTATE_COMBAT: { // dead enemy if ( HasConditions( bits_COND_ENEMY_DEAD ) ) { // call base class, all code to handle dead enemies is centralized there. return CBaseMonster :: GetSchedule(); } // always act surprized with a new enemy if ( HasConditions( bits_COND_NEW_ENEMY ) && HasConditions( bits_COND_LIGHT_DAMAGE) ) return GetScheduleOfType( SCHED_SMALL_FLINCH ); // wait for one schedule to draw gun if (!m_fGunDrawn ) return GetScheduleOfType( SCHED_ARM_WEAPON ); if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); // no ammo else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) { //!!!KELLY - this individual just realized he's out of bullet ammo. // He's going to try to find cover to run to and reload, but rarely, if // none is available, he'll drop and reload in the open here. return GetScheduleOfType ( SCHED_GRUNT_COVER_AND_RELOAD ); } //new add //enemy is occluded else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) { if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) ) { //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc if (FOkToSpeak()) { } return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); } } } break; case MONSTERSTATE_ALERT: case MONSTERSTATE_IDLE: if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) { // flinch if hurt return GetScheduleOfType( SCHED_SMALL_FLINCH ); } if ( m_hEnemy == NULL && IsFollowing() ) { if ( !m_hTargetEnt->IsAlive() ) { // UNDONE: Comment about the recently dead player here? StopFollowing( FALSE ); break; } else { if ( HasConditions( bits_COND_CLIENT_PUSH ) ) { return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); } return GetScheduleOfType( SCHED_TARGET_FACE ); } } if ( HasConditions( bits_COND_CLIENT_PUSH ) ) { return GetScheduleOfType( SCHED_MOVE_AWAY ); } // try to say something about smells TrySmellTalk(); break; } return CTalkMonster::GetSchedule(); }