/*** * * 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. * ****/ #if !OEM_BUILD && !HLDEMO_BUILD //========================================================= // hassassin - Human assassin, fast and stealthy //========================================================= #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "schedule.h" #include "squadmonster.h" #include "weapons.h" #include "soundent.h" #include "game.h" extern DLL_GLOBAL int g_iSkillLevel; //========================================================= // monster-specific schedule types //========================================================= enum { SCHED_ASSASSIN_EXPOSED = LAST_COMMON_SCHEDULE + 1,// cover was blown. SCHED_ASSASSIN_JUMP, // fly through the air SCHED_ASSASSIN_JUMP_ATTACK, // fly through the air and shoot SCHED_ASSASSIN_JUMP_LAND // hit and run away }; //========================================================= // monster-specific tasks //========================================================= enum { TASK_ASSASSIN_FALL_TO_GROUND = LAST_COMMON_TASK + 1 // falling and waiting to hit ground }; //========================================================= // Monster's Anim Events Go Here //========================================================= #define ASSASSIN_AE_SHOOT1 1 #define ASSASSIN_AE_TOSS1 2 #define ASSASSIN_AE_JUMP 3 #define bits_MEMORY_BADJUMP ( bits_MEMORY_CUSTOM1 ) class CHAssassin : public CBaseMonster { public: void Spawn( void ); void Precache( void ); void SetYawSpeed ( void ); int Classify( void ); int ISoundMask( void); void Shoot( void ); int IRelationship( CBaseEntity *pTarget ); void HandleAnimEvent( MonsterEvent_t *pEvent ); Schedule_t *GetSchedule( void ); Schedule_t *GetScheduleOfType( int Type ); BOOL CheckMeleeAttack1( float flDot, float flDist ); // jump // BOOL CheckMeleeAttack2( float flDot, float flDist ); BOOL CheckRangeAttack1( float flDot, float flDist ); // shoot BOOL CheckRangeAttack2( float flDot, float flDist ); // throw grenade void StartTask( Task_t *pTask ); void RunAI( void ); void RunTask( Task_t *pTask ); void DeathSound( void ); void IdleSound( void ); CUSTOM_SCHEDULES int Save( CSave &save ); int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; float m_flLastShot; float m_flDiviation; float m_flNextJump; Vector m_vecJumpVelocity; float m_flNextGrenadeCheck; Vector m_vecTossVelocity; BOOL m_fThrowGrenade; int m_iTargetRanderamt; int m_iFrustration; int m_iShell; }; LINK_ENTITY_TO_CLASS( monster_human_assassin, CHAssassin ) TYPEDESCRIPTION CHAssassin::m_SaveData[] = { DEFINE_FIELD( CHAssassin, m_flLastShot, FIELD_TIME ), DEFINE_FIELD( CHAssassin, m_flDiviation, FIELD_FLOAT ), DEFINE_FIELD( CHAssassin, m_flNextJump, FIELD_TIME ), DEFINE_FIELD( CHAssassin, m_vecJumpVelocity, FIELD_VECTOR ), DEFINE_FIELD( CHAssassin, m_flNextGrenadeCheck, FIELD_TIME ), DEFINE_FIELD( CHAssassin, m_vecTossVelocity, FIELD_VECTOR ), DEFINE_FIELD( CHAssassin, m_fThrowGrenade, FIELD_BOOLEAN ), DEFINE_FIELD( CHAssassin, m_iTargetRanderamt, FIELD_INTEGER ), DEFINE_FIELD( CHAssassin, m_iFrustration, FIELD_INTEGER ), }; IMPLEMENT_SAVERESTORE( CHAssassin, CBaseMonster ) //========================================================= // DieSound //========================================================= void CHAssassin::DeathSound( void ) { } //========================================================= // IdleSound //========================================================= void CHAssassin::IdleSound( void ) { } //========================================================= // ISoundMask - returns a bit mask indicating which types // of sounds this monster regards. //========================================================= int CHAssassin::ISoundMask( void ) { return bits_SOUND_WORLD | bits_SOUND_COMBAT | bits_SOUND_DANGER | bits_SOUND_PLAYER; } //========================================================= // Classify - indicates this monster's place in the // relationship table. //========================================================= int CHAssassin::Classify( void ) { return CLASS_HUMAN_MILITARY; } //========================================================= // SetYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= void CHAssassin::SetYawSpeed( void ) { int ys; switch( m_Activity ) { case ACT_TURN_LEFT: case ACT_TURN_RIGHT: ys = 360; break; default: ys = 360; break; } pev->yaw_speed = ys; } //========================================================= // Shoot //========================================================= void CHAssassin::Shoot( void ) { if( m_hEnemy == 0 ) { return; } Vector vecShootOrigin = GetGunPosition(); Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); if( m_flLastShot + 2.0f < gpGlobals->time ) { m_flDiviation = 0.10f; } else { m_flDiviation -= 0.01f; if( m_flDiviation < 0.02f ) m_flDiviation = 0.02f; } m_flLastShot = gpGlobals->time; UTIL_MakeVectors( pev->angles ); Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT( 40, 90 ) + gpGlobals->v_up * RANDOM_FLOAT( 75, 200 ) + gpGlobals->v_forward * RANDOM_FLOAT( -40, 40 ); EjectBrass( pev->origin + gpGlobals->v_up * 32 + gpGlobals->v_forward * 12, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL ); FireBullets( 1, vecShootOrigin, vecShootDir, Vector( m_flDiviation, m_flDiviation, m_flDiviation ), 2048, BULLET_MONSTER_9MM ); // shoot +-8 degrees switch( RANDOM_LONG( 0, 1 ) ) { case 0: EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/pl_gun1.wav", RANDOM_FLOAT( 0.6f, 0.8f ), ATTN_NORM ); break; case 1: EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/pl_gun2.wav", RANDOM_FLOAT( 0.6f, 0.8f ), ATTN_NORM ); break; } pev->effects |= EF_MUZZLEFLASH; Vector angDir = UTIL_VecToAngles( vecShootDir ); SetBlending( 0, angDir.x ); m_cAmmoLoaded--; } //========================================================= // IRelationship - overridden because Human Grunts are // BlackOps's nemesis. //========================================================= int CHAssassin::IRelationship( CBaseEntity *pTarget ) { if( FClassnameIs( pTarget->pev, "monster_human_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_apache" ) ) ) { return R_NM; } return CBaseMonster::IRelationship( pTarget ); } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. // // Returns number of events handled, 0 if none. //========================================================= void CHAssassin::HandleAnimEvent( MonsterEvent_t *pEvent ) { switch( pEvent->event ) { case ASSASSIN_AE_SHOOT1: Shoot(); break; case ASSASSIN_AE_TOSS1: { UTIL_MakeVectors( pev->angles ); CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector( 0, 0, 32 ), m_vecTossVelocity, 2.0 ); m_flNextGrenadeCheck = gpGlobals->time + 6.0f;// wait six seconds before even looking again to see if a grenade can be thrown. m_fThrowGrenade = FALSE; // !!!LATER - when in a group, only try to throw grenade if ordered. } break; case ASSASSIN_AE_JUMP: { // ALERT( at_console, "jumping"); UTIL_MakeAimVectors( pev->angles ); pev->movetype = MOVETYPE_TOSS; pev->flags &= ~FL_ONGROUND; pev->velocity = m_vecJumpVelocity; m_flNextJump = gpGlobals->time + 3.0f; } return; default: CBaseMonster::HandleAnimEvent( pEvent ); break; } } //========================================================= // Spawn //========================================================= void CHAssassin::Spawn() { Precache(); SET_MODEL( ENT( pev ), "models/hassassin.mdl" ); UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_RED; pev->effects = 0; pev->health = gSkillData.hassassinHealth; m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) m_MonsterState = MONSTERSTATE_NONE; m_afCapability = bits_CAP_MELEE_ATTACK1 | bits_CAP_DOORS_GROUP; pev->friction = 1; m_HackedGunPos = Vector( 0, 24, 48 ); m_iTargetRanderamt = 20; pev->renderamt = 20; pev->rendermode = kRenderTransTexture; MonsterInit(); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CHAssassin::Precache() { PRECACHE_MODEL( "models/hassassin.mdl" ); PRECACHE_SOUND( "weapons/pl_gun1.wav" ); PRECACHE_SOUND( "weapons/pl_gun2.wav" ); PRECACHE_SOUND( "debris/beamstart1.wav" ); m_iShell = PRECACHE_MODEL( "models/shell.mdl" );// brass shell } //========================================================= // AI Schedules Specific to this monster //========================================================= //========================================================= // Fail Schedule //========================================================= Task_t tlAssassinFail[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT_FACE_ENEMY, (float)2 }, // { TASK_WAIT_PVS, (float)0 }, { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, }; Schedule_t slAssassinFail[] = { { tlAssassinFail, ARRAYSIZE( tlAssassinFail ), bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_PROVOKED | bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_HEAR_SOUND, bits_SOUND_DANGER | bits_SOUND_PLAYER, "AssassinFail" }, }; //========================================================= // Enemy exposed Agrunt's cover //========================================================= Task_t tlAssassinExposed[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP }, { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, }; Schedule_t slAssassinExposed[] = { { tlAssassinExposed, ARRAYSIZE( tlAssassinExposed ), bits_COND_CAN_MELEE_ATTACK1, 0, "AssassinExposed", }, }; //========================================================= // Take cover from enemy! Tries lateral cover before node // cover! //========================================================= Task_t tlAssassinTakeCoverFromEnemy[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_WAIT, (float)0.2 }, { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, { TASK_RUN_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, { TASK_FACE_ENEMY, (float)0 }, }; Schedule_t slAssassinTakeCoverFromEnemy[] = { { tlAssassinTakeCoverFromEnemy, ARRAYSIZE( tlAssassinTakeCoverFromEnemy ), bits_COND_NEW_ENEMY | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "AssassinTakeCoverFromEnemy" }, }; //========================================================= // Take cover from enemy! Tries lateral cover before node // cover! //========================================================= Task_t tlAssassinTakeCoverFromEnemy2[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_WAIT, (float)0.2 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK2 }, { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, { TASK_RUN_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, { TASK_FACE_ENEMY, (float)0 }, }; Schedule_t slAssassinTakeCoverFromEnemy2[] = { { tlAssassinTakeCoverFromEnemy2, ARRAYSIZE( tlAssassinTakeCoverFromEnemy2 ), bits_COND_NEW_ENEMY | bits_COND_CAN_MELEE_ATTACK2 | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "AssassinTakeCoverFromEnemy2" }, }; //========================================================= // hide from the loudest sound source //========================================================= Task_t tlAssassinTakeCoverFromBestSound[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, { TASK_STOP_MOVING, (float)0 }, { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, { TASK_RUN_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, { TASK_TURN_LEFT, (float)179 }, }; Schedule_t slAssassinTakeCoverFromBestSound[] = { { tlAssassinTakeCoverFromBestSound, ARRAYSIZE( tlAssassinTakeCoverFromBestSound ), bits_COND_NEW_ENEMY, 0, "AssassinTakeCoverFromBestSound" }, }; //========================================================= // AlertIdle Schedules //========================================================= Task_t tlAssassinHide[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT, (float)2 }, { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, }; Schedule_t slAssassinHide[] = { { tlAssassinHide, ARRAYSIZE( tlAssassinHide ), bits_COND_NEW_ENEMY | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_PROVOKED | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "AssassinHide" }, }; //========================================================= // HUNT Schedules //========================================================= Task_t tlAssassinHunt[] = { { TASK_GET_PATH_TO_ENEMY, (float)0 }, { TASK_RUN_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, }; Schedule_t slAssassinHunt[] = { { tlAssassinHunt, ARRAYSIZE( tlAssassinHunt ), bits_COND_NEW_ENEMY | // bits_COND_SEE_ENEMY | bits_COND_CAN_RANGE_ATTACK1 | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "AssassinHunt" }, }; //========================================================= // Jumping Schedules //========================================================= Task_t tlAssassinJump[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, { TASK_SET_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_ATTACK }, }; Schedule_t slAssassinJump[] = { { tlAssassinJump, ARRAYSIZE( tlAssassinJump ), 0, 0, "AssassinJump" }, }; //========================================================= // repel //========================================================= Task_t tlAssassinJumpAttack[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_LAND }, // { TASK_SET_ACTIVITY, (float)ACT_FLY }, { TASK_ASSASSIN_FALL_TO_GROUND, (float)0 }, }; Schedule_t slAssassinJumpAttack[] = { { tlAssassinJumpAttack, ARRAYSIZE( tlAssassinJumpAttack ), 0, 0, "AssassinJumpAttack" }, }; //========================================================= // repel //========================================================= Task_t tlAssassinJumpLand[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_EXPOSED }, // { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_REMEMBER, (float)bits_MEMORY_BADJUMP }, { TASK_FIND_NODE_COVER_FROM_ENEMY, (float)0 }, { TASK_RUN_PATH, (float)0 }, { TASK_FORGET, (float)bits_MEMORY_BADJUMP }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, { TASK_FACE_ENEMY, (float)0 }, { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, }; Schedule_t slAssassinJumpLand[] = { { tlAssassinJumpLand, ARRAYSIZE( tlAssassinJumpLand ), 0, 0, "AssassinJumpLand" }, }; DEFINE_CUSTOM_SCHEDULES( CHAssassin ) { slAssassinFail, slAssassinExposed, slAssassinTakeCoverFromEnemy, slAssassinTakeCoverFromEnemy2, slAssassinTakeCoverFromBestSound, slAssassinHide, slAssassinHunt, slAssassinJump, slAssassinJumpAttack, slAssassinJumpLand, }; IMPLEMENT_CUSTOM_SCHEDULES( CHAssassin, CBaseMonster ) //========================================================= // CheckMeleeAttack1 - jump like crazy if the enemy gets too close. //========================================================= BOOL CHAssassin::CheckMeleeAttack1( float flDot, float flDist ) { if( m_flNextJump < gpGlobals->time && ( flDist <= 128.0f || HasMemory( bits_MEMORY_BADJUMP ) ) && m_hEnemy != 0 ) { TraceResult tr; Vector vecDest = pev->origin + Vector( RANDOM_FLOAT( -64, 64), RANDOM_FLOAT( -64, 64 ), 160 ); UTIL_TraceHull( pev->origin + Vector( 0, 0, 36 ), vecDest + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, ENT( pev ), &tr ); if( tr.fStartSolid || tr.flFraction < 1.0f ) { return FALSE; } float flGravity = g_psv_gravity->value; float time = sqrt( 160.0f / ( 0.5f * flGravity ) ); float speed = flGravity * time / 160.0f; m_vecJumpVelocity = ( vecDest - pev->origin ) * speed; return TRUE; } return FALSE; } //========================================================= // CheckRangeAttack1 - drop a cap in their ass // //========================================================= BOOL CHAssassin::CheckRangeAttack1( float flDot, float flDist ) { if( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist > 64 && flDist <= 2048 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) { TraceResult tr; Vector vecSrc = GetGunPosition(); // verify that a bullet fired from the gun will hit the enemy before the world. UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget( vecSrc ), dont_ignore_monsters, ENT( pev ), &tr ); if( tr.flFraction == 1 || tr.pHit == m_hEnemy->edict() ) { return TRUE; } } return FALSE; } //========================================================= // CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close. //========================================================= BOOL CHAssassin::CheckRangeAttack2( float flDot, float flDist ) { m_fThrowGrenade = FALSE; if( !FBitSet( m_hEnemy->pev->flags, FL_ONGROUND ) ) { // don't throw grenades at anything that isn't on the ground! return FALSE; } // don't get grenade happy unless the player starts to piss you off if( m_iFrustration <= 2 ) return FALSE; if( m_flNextGrenadeCheck < gpGlobals->time && !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 512 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) { Vector vecToss = VecCheckThrow( pev, GetGunPosition(), m_hEnemy->Center(), flDist, 0.5 ); // use dist as speed to get there in 1 second if( vecToss != g_vecZero ) { m_vecTossVelocity = vecToss; // throw a hand grenade m_fThrowGrenade = TRUE; return TRUE; } } return FALSE; } //========================================================= // RunAI //========================================================= void CHAssassin::RunAI( void ) { CBaseMonster::RunAI(); // always visible if moving // always visible is not on hard if( g_iSkillLevel != SKILL_HARD || m_hEnemy == 0 || pev->deadflag != DEAD_NO || m_Activity == ACT_RUN || m_Activity == ACT_WALK || !( pev->flags & FL_ONGROUND ) ) m_iTargetRanderamt = 255; else m_iTargetRanderamt = 20; if( pev->renderamt > m_iTargetRanderamt ) { if( pev->renderamt == 255 ) { EMIT_SOUND( ENT( pev ), CHAN_BODY, "debris/beamstart1.wav", 0.2, ATTN_NORM ); } pev->renderamt = Q_max( pev->renderamt - 50, m_iTargetRanderamt ); pev->rendermode = kRenderTransTexture; } else if( pev->renderamt < m_iTargetRanderamt ) { pev->renderamt = Q_min( pev->renderamt + 50, m_iTargetRanderamt ); if( pev->renderamt == 255 ) pev->rendermode = kRenderNormal; } if( m_Activity == ACT_RUN || m_Activity == ACT_WALK ) { static int iStep = 0; iStep = !iStep; if( iStep ) { switch( RANDOM_LONG( 0, 3 ) ) { case 0: EMIT_SOUND( ENT( pev ), CHAN_BODY, "player/pl_step1.wav", 0.5, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT( pev ), CHAN_BODY, "player/pl_step3.wav", 0.5, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT( pev ), CHAN_BODY, "player/pl_step2.wav", 0.5, ATTN_NORM ); break; case 3: EMIT_SOUND( ENT( pev ), CHAN_BODY, "player/pl_step4.wav", 0.5, ATTN_NORM ); break; } } } } //========================================================= // StartTask //========================================================= void CHAssassin::StartTask( Task_t *pTask ) { switch( pTask->iTask ) { case TASK_RANGE_ATTACK2: if( !m_fThrowGrenade ) { TaskComplete(); } else { CBaseMonster::StartTask( pTask ); } break; case TASK_ASSASSIN_FALL_TO_GROUND: break; default: CBaseMonster::StartTask( pTask ); break; } } //========================================================= // RunTask //========================================================= void CHAssassin::RunTask( Task_t *pTask ) { switch( pTask->iTask ) { case TASK_ASSASSIN_FALL_TO_GROUND: MakeIdealYaw( m_vecEnemyLKP ); ChangeYaw( pev->yaw_speed ); if( m_fSequenceFinished ) { if( pev->velocity.z > 0 ) { pev->sequence = LookupSequence( "fly_up" ); } else if( HasConditions( bits_COND_SEE_ENEMY ) ) { pev->sequence = LookupSequence( "fly_attack" ); pev->frame = 0; } else { pev->sequence = LookupSequence( "fly_down" ); pev->frame = 0; } ResetSequenceInfo(); SetYawSpeed(); } if( pev->flags & FL_ONGROUND ) { // ALERT( at_console, "on ground\n" ); TaskComplete(); } break; default: CBaseMonster::RunTask( pTask ); 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; default: break; } return CBaseMonster::GetSchedule(); } //========================================================= //========================================================= Schedule_t *CHAssassin::GetScheduleOfType( int Type ) { // ALERT( at_console, "%d\n", m_iFrustration ); switch( Type ) { case SCHED_TAKE_COVER_FROM_ENEMY: if( pev->health > 30 ) return slAssassinTakeCoverFromEnemy; else return slAssassinTakeCoverFromEnemy2; case SCHED_TAKE_COVER_FROM_BEST_SOUND: return slAssassinTakeCoverFromBestSound; case SCHED_ASSASSIN_EXPOSED: return slAssassinExposed; case SCHED_FAIL: if( m_MonsterState == MONSTERSTATE_COMBAT ) return slAssassinFail; break; case SCHED_ALERT_STAND: if( m_MonsterState == MONSTERSTATE_COMBAT ) return slAssassinHide; break; case SCHED_CHASE_ENEMY: return slAssassinHunt; case SCHED_MELEE_ATTACK1: if( pev->flags & FL_ONGROUND ) { if( m_flNextJump > gpGlobals->time ) { // can't jump yet, go ahead and fail return slAssassinFail; } else { return slAssassinJump; } } else { return slAssassinJumpAttack; } case SCHED_ASSASSIN_JUMP: case SCHED_ASSASSIN_JUMP_ATTACK: return slAssassinJumpAttack; case SCHED_ASSASSIN_JUMP_LAND: return slAssassinJumpLand; } return CBaseMonster::GetScheduleOfType( Type ); } #endif