/*** * * PARANOIA 2: SAVIOR * MaSTeR * ****/ //========================================================= // headcrab.cpp - tiny, jumpy alien parasite //========================================================= #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "schedule.h" #include "game.h" //========================================================= // Monster's Anim Events Go Here //========================================================= #define SPIDER_AE_JUMPATTACK ( 2 ) Task_t tlSpiderRangeAttack1[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_IDEAL, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_FACE_IDEAL, (float)0 }, { TASK_WAIT_RANDOM, (float)0.5 }, }; Schedule_t slSpiderRangeAttack1[] = { { tlSpiderRangeAttack1, ARRAYSIZE ( tlSpiderRangeAttack1 ), bits_COND_ENEMY_OCCLUDED | bits_COND_NO_AMMO_LOADED, 0, "HCRangeAttack1" }, }; Task_t tlSpiderRangeAttack1Fast[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_IDEAL, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, }; Schedule_t slSpiderRangeAttack1Fast[] = { { tlSpiderRangeAttack1Fast, ARRAYSIZE ( tlSpiderRangeAttack1Fast ), bits_COND_ENEMY_OCCLUDED | bits_COND_NO_AMMO_LOADED, 0, "HCRAFast" }, }; class CSpider : public CBaseMonster { public: void Spawn( void ); void Precache( void ); void RunTask ( Task_t *pTask ); void StartTask ( Task_t *pTask ); float MaxYawSpeed( void ); void EXPORT LeapTouch ( CBaseEntity *pOther ); Vector Center( void ); Vector BodyTarget( const Vector &posSrc ); void PainSound( void ); void DeathSound( void ); void IdleSound( void ); void AlertSound( void ); void PrescheduleThink( void ); int Classify ( void ); void HandleAnimEvent( MonsterEvent_t *pEvent ); BOOL CheckRangeAttack1 ( float flDot, float flDist ); BOOL CheckRangeAttack2 ( float flDot, float flDist ); int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); virtual float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite; } virtual int GetVoicePitch( void ) { return 100; } virtual float GetSoundVolue( void ) { return 1.0; } Schedule_t* GetScheduleOfType ( int Type ); CUSTOM_SCHEDULES; static const char *pIdleSounds[]; static const char *pAlertSounds[]; static const char *pPainSounds[]; static const char *pAttackSounds[]; static const char *pDeathSounds[]; static const char *pBiteSounds[]; }; LINK_ENTITY_TO_CLASS( monster_spider, CSpider ); DEFINE_CUSTOM_SCHEDULES( CSpider ) { slSpiderRangeAttack1, slSpiderRangeAttack1Fast, }; IMPLEMENT_CUSTOM_SCHEDULES( CSpider, CBaseMonster ); const char *CSpider::pIdleSounds[] = { "spider/idle1.wav", "spider/idle2.wav", "spider/idle3.wav", }; const char *CSpider::pAlertSounds[] = { "spider/alert1.wav", }; const char *CSpider::pPainSounds[] = { "spider/pain1.wav", "spider/pain2.wav", "spider/pain3.wav", }; const char *CSpider::pAttackSounds[] = { "spider/attack1.wav", "spider/attack2.wav", "spider/attack3.wav", }; const char *CSpider::pDeathSounds[] = { "spider/die1.wav", "spider/die2.wav", }; const char *CSpider::pBiteSounds[] = { "spider/headbite.wav", }; //========================================================= // Classify - indicates this monster's place in the // relationship table. //========================================================= int CSpider :: Classify ( void ) { return m_iClass?m_iClass:CLASS_ALIEN_PREY; } //========================================================= // Center - returns the real center of the spider. The // bounding box is much larger than the actual creature so // this is needed for targeting //========================================================= Vector CSpider :: Center ( void ) { return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 6 ); } Vector CSpider :: BodyTarget( const Vector &posSrc ) { return Center( ); } //========================================================= // MaxYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= float CSpider :: MaxYawSpeed( void ) { float ys; switch ( m_Activity ) { case ACT_IDLE: ys = 30; break; case ACT_RUN: case ACT_WALK: ys = 20; break; case ACT_TURN_LEFT: case ACT_TURN_RIGHT: ys = 60; break; case ACT_RANGE_ATTACK1: ys = 30; break; default: ys = 30; break; } return ys; } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. //========================================================= void CSpider :: HandleAnimEvent( MonsterEvent_t *pEvent ) { switch( pEvent->event ) { case SPIDER_AE_JUMPATTACK: { ClearBits( pev->flags, FL_ONGROUND ); UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground UTIL_MakeVectors ( pev->angles ); Vector vecJumpDir; if (m_hEnemy != NULL) { float gravity = g_psv_gravity->value; if (gravity <= 1) gravity = 1; // How fast does the spider need to travel to reach that height given gravity? float height = (m_hEnemy->pev->origin.z + m_hEnemy->pev->view_ofs.z - pev->origin.z); if (height < 16) height = 16; float speed = sqrt( 2 * gravity * height ); float time = speed / gravity; // Scale the sideways velocity to get there at the right time vecJumpDir = (m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs - pev->origin); vecJumpDir = vecJumpDir * ( 1.0 / time ); // Speed to offset gravity at the desired height vecJumpDir.z = speed; // Don't jump too far/fast float distance = vecJumpDir.Length(); if (distance > 650) { vecJumpDir = vecJumpDir * ( 650.0 / distance ); } } else { // jump hop, don't care where vecJumpDir = Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_up.z ) * 350; } int iSound = RANDOM_LONG(0,2); if ( iSound != 0 ) EMIT_SOUND_DYN( edict(), CHAN_VOICE, pAttackSounds[iSound], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); pev->velocity = vecJumpDir; m_flNextAttack = gpGlobals->time + 2; } break; default: CBaseMonster::HandleAnimEvent( pEvent ); break; } } //========================================================= // Spawn //========================================================= void CSpider :: Spawn() { Precache( ); if (pev->model) SET_MODEL(ENT(pev), STRING(pev->model)); //LRC else SET_MODEL(ENT(pev), "models/monsters/monster_spider.mdl"); UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_GREEN; if (pev->health == 0) pev->health = gSkillData.headcrabHealth; pev->view_ofs = Vector ( 0, 0, 20 );// position of the eyes relative to monster's origin. m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) m_MonsterState = MONSTERSTATE_NONE; MonsterInit(); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CSpider :: Precache() { PRECACHE_SOUND_ARRAY(pIdleSounds); PRECACHE_SOUND_ARRAY(pAlertSounds); PRECACHE_SOUND_ARRAY(pPainSounds); PRECACHE_SOUND_ARRAY(pAttackSounds); PRECACHE_SOUND_ARRAY(pDeathSounds); PRECACHE_SOUND_ARRAY(pBiteSounds); if (pev->model) PRECACHE_MODEL((char*)STRING(pev->model)); //LRC else PRECACHE_MODEL("models/monsters/monster_spider.mdl"); } //========================================================= // RunTask //========================================================= void CSpider :: RunTask ( Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_RANGE_ATTACK1: case TASK_RANGE_ATTACK2: { if ( m_fSequenceFinished ) { TaskComplete(); SetTouch( NULL ); m_IdealActivity = ACT_IDLE; } break; } default: { CBaseMonster :: RunTask(pTask); } } } //========================================================= // LeapTouch - this is the spider's touch function when it // is in the air //========================================================= void CSpider :: LeapTouch ( CBaseEntity *pOther ) { if ( !pOther->pev->takedamage ) { return; } if ( pOther->Classify() == Classify() ) { return; } // Don't hit if back on ground if ( !FBitSet( pev->flags, FL_ONGROUND ) ) { EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBiteSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); pOther->TakeDamage( pev, pev, GetDamageAmount(), DMG_SLASH ); } SetTouch( NULL ); } //========================================================= // PrescheduleThink //========================================================= void CSpider :: PrescheduleThink ( void ) { // make the crab coo a little bit in combat state if ( m_MonsterState == MONSTERSTATE_COMBAT && RANDOM_FLOAT( 0, 5 ) < 0.1 ) { IdleSound(); } } void CSpider :: StartTask ( Task_t *pTask ) { m_iTaskStatus = TASKSTATUS_RUNNING; switch ( pTask->iTask ) { case TASK_RANGE_ATTACK1: { EMIT_SOUND_DYN( edict(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); m_IdealActivity = ACT_RANGE_ATTACK1; SetTouch(&CSpider :: LeapTouch ); break; } default: { CBaseMonster :: StartTask( pTask ); } } } //========================================================= // CheckRangeAttack1 //========================================================= BOOL CSpider :: CheckRangeAttack1 ( float flDot, float flDist ) { if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist <= 256 && flDot >= 0.65 ) { return TRUE; } return FALSE; } //========================================================= // CheckRangeAttack2 //========================================================= BOOL CSpider :: CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; // BUGBUG: Why is this code here? There is no ACT_RANGE_ATTACK2 animation. I've disabled it for now. #if 0 if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist > 64 && flDist <= 256 && flDot >= 0.5 ) { return TRUE; } return FALSE; #endif } int CSpider :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { // Don't take any acid damage -- BigMomma's mortar is acid if ( bitsDamageType & DMG_ACID ) flDamage = 0; return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); } //========================================================= // IdleSound //========================================================= #define SPIDER_ATTN_IDLE (float)1.5 void CSpider :: IdleSound ( void ) { EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); } //========================================================= // AlertSound //========================================================= void CSpider :: AlertSound ( void ) { EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); } //========================================================= // AlertSound //========================================================= void CSpider :: PainSound ( void ) { EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); } //========================================================= // DeathSound //========================================================= void CSpider :: DeathSound ( void ) { EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); } Schedule_t* CSpider :: GetScheduleOfType ( int Type ) { switch ( Type ) { case SCHED_RANGE_ATTACK1: { return &slSpiderRangeAttack1[ 0 ]; } break; } return CBaseMonster::GetScheduleOfType( Type ); }