You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
555 lines
15 KiB
555 lines
15 KiB
/*** |
|
* |
|
* 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. |
|
* |
|
****/ |
|
//========================================================= |
|
// 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 HC_AE_JUMPATTACK ( 2 ) |
|
|
|
Task_t tlHCRangeAttack1[] = |
|
{ |
|
{ 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 slHCRangeAttack1[] = |
|
{ |
|
{ |
|
tlHCRangeAttack1, |
|
ARRAYSIZE ( tlHCRangeAttack1 ), |
|
bits_COND_ENEMY_OCCLUDED | |
|
bits_COND_NO_AMMO_LOADED, |
|
0, |
|
"HCRangeAttack1" |
|
}, |
|
}; |
|
|
|
Task_t tlHCRangeAttack1Fast[] = |
|
{ |
|
{ TASK_STOP_MOVING, (float)0 }, |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_RANGE_ATTACK1, (float)0 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
}; |
|
|
|
Schedule_t slHCRangeAttack1Fast[] = |
|
{ |
|
{ |
|
tlHCRangeAttack1Fast, |
|
ARRAYSIZE ( tlHCRangeAttack1Fast ), |
|
bits_COND_ENEMY_OCCLUDED | |
|
bits_COND_NO_AMMO_LOADED, |
|
0, |
|
"HCRAFast" |
|
}, |
|
}; |
|
|
|
class CHeadCrab : public CBaseMonster |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void Precache( void ); |
|
void RunTask ( Task_t *pTask ); |
|
void StartTask ( Task_t *pTask ); |
|
void SetYawSpeed ( 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_headcrab, CHeadCrab ); |
|
|
|
DEFINE_CUSTOM_SCHEDULES( CHeadCrab ) |
|
{ |
|
slHCRangeAttack1, |
|
slHCRangeAttack1Fast, |
|
}; |
|
|
|
IMPLEMENT_CUSTOM_SCHEDULES( CHeadCrab, CBaseMonster ); |
|
|
|
const char *CHeadCrab::pIdleSounds[] = |
|
{ |
|
"headcrab/hc_idle1.wav", |
|
"headcrab/hc_idle2.wav", |
|
"headcrab/hc_idle3.wav", |
|
}; |
|
const char *CHeadCrab::pAlertSounds[] = |
|
{ |
|
"headcrab/hc_alert1.wav", |
|
}; |
|
const char *CHeadCrab::pPainSounds[] = |
|
{ |
|
"headcrab/hc_pain1.wav", |
|
"headcrab/hc_pain2.wav", |
|
"headcrab/hc_pain3.wav", |
|
}; |
|
const char *CHeadCrab::pAttackSounds[] = |
|
{ |
|
"headcrab/hc_attack1.wav", |
|
"headcrab/hc_attack2.wav", |
|
"headcrab/hc_attack3.wav", |
|
}; |
|
|
|
const char *CHeadCrab::pDeathSounds[] = |
|
{ |
|
"headcrab/hc_die1.wav", |
|
"headcrab/hc_die2.wav", |
|
}; |
|
|
|
const char *CHeadCrab::pBiteSounds[] = |
|
{ |
|
"headcrab/hc_headbite.wav", |
|
}; |
|
|
|
//========================================================= |
|
// Classify - indicates this monster's place in the |
|
// relationship table. |
|
//========================================================= |
|
int CHeadCrab :: Classify ( void ) |
|
{ |
|
return CLASS_ALIEN_PREY; |
|
} |
|
|
|
//========================================================= |
|
// Center - returns the real center of the headcrab. The |
|
// bounding box is much larger than the actual creature so |
|
// this is needed for targeting |
|
//========================================================= |
|
Vector CHeadCrab :: Center ( void ) |
|
{ |
|
return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 6 ); |
|
} |
|
|
|
|
|
Vector CHeadCrab :: BodyTarget( const Vector &posSrc ) |
|
{ |
|
return Center( ); |
|
} |
|
|
|
//========================================================= |
|
// SetYawSpeed - allows each sequence to have a different |
|
// turn rate associated with it. |
|
//========================================================= |
|
void CHeadCrab :: SetYawSpeed ( void ) |
|
{ |
|
int 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; |
|
} |
|
|
|
pev->yaw_speed = ys; |
|
} |
|
|
|
//========================================================= |
|
// HandleAnimEvent - catches the monster-specific messages |
|
// that occur when tagged animation frames are played. |
|
//========================================================= |
|
void CHeadCrab :: HandleAnimEvent( MonsterEvent_t *pEvent ) |
|
{ |
|
switch( pEvent->event ) |
|
{ |
|
case HC_AE_JUMPATTACK: |
|
{ |
|
ClearBits( pev->flags, FL_ONGROUND ); |
|
|
|
UTIL_SetOrigin (pev, 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 headcrab 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 CHeadCrab :: Spawn() |
|
{ |
|
Precache( ); |
|
|
|
SET_MODEL(ENT(pev), "models/headcrab.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; |
|
pev->effects = 0; |
|
pev->health = gSkillData.headcrabHealth; |
|
pev->view_ofs = Vector ( 0, 0, 20 );// position of the eyes relative to monster's origin. |
|
pev->yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? |
|
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 CHeadCrab :: 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); |
|
|
|
PRECACHE_MODEL("models/headcrab.mdl"); |
|
} |
|
|
|
|
|
//========================================================= |
|
// RunTask |
|
//========================================================= |
|
void CHeadCrab :: 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 headcrab's touch function when it |
|
// is in the air |
|
//========================================================= |
|
void CHeadCrab :: 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 CHeadCrab :: 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 CHeadCrab :: 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( &CHeadCrab::LeapTouch ); |
|
break; |
|
} |
|
default: |
|
{ |
|
CBaseMonster :: StartTask( pTask ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//========================================================= |
|
// CheckRangeAttack1 |
|
//========================================================= |
|
BOOL CHeadCrab :: CheckRangeAttack1 ( float flDot, float flDist ) |
|
{ |
|
if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist <= 256 && flDot >= 0.65 ) |
|
{ |
|
return TRUE; |
|
} |
|
return FALSE; |
|
} |
|
|
|
//========================================================= |
|
// CheckRangeAttack2 |
|
//========================================================= |
|
BOOL CHeadCrab :: 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 CHeadCrab :: 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 CRAB_ATTN_IDLE (float)1.5 |
|
void CHeadCrab :: IdleSound ( void ) |
|
{ |
|
EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); |
|
} |
|
|
|
//========================================================= |
|
// AlertSound |
|
//========================================================= |
|
void CHeadCrab :: AlertSound ( void ) |
|
{ |
|
EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); |
|
} |
|
|
|
//========================================================= |
|
// AlertSound |
|
//========================================================= |
|
void CHeadCrab :: PainSound ( void ) |
|
{ |
|
EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); |
|
} |
|
|
|
//========================================================= |
|
// DeathSound |
|
//========================================================= |
|
void CHeadCrab :: DeathSound ( void ) |
|
{ |
|
EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); |
|
} |
|
|
|
Schedule_t* CHeadCrab :: GetScheduleOfType ( int Type ) |
|
{ |
|
switch ( Type ) |
|
{ |
|
case SCHED_RANGE_ATTACK1: |
|
{ |
|
return &slHCRangeAttack1[ 0 ]; |
|
} |
|
break; |
|
} |
|
|
|
return CBaseMonster::GetScheduleOfType( Type ); |
|
} |
|
|
|
|
|
class CBabyCrab : public CHeadCrab |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void Precache( void ); |
|
void SetYawSpeed ( void ); |
|
float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite * 0.3; } |
|
BOOL CheckRangeAttack1 ( float flDot, float flDist ); |
|
Schedule_t* GetScheduleOfType ( int Type ); |
|
virtual int GetVoicePitch( void ) { return PITCH_NORM + RANDOM_LONG(40,50); } |
|
virtual float GetSoundVolue( void ) { return 0.8; } |
|
}; |
|
LINK_ENTITY_TO_CLASS( monster_babycrab, CBabyCrab ); |
|
|
|
void CBabyCrab :: Spawn( void ) |
|
{ |
|
CHeadCrab::Spawn(); |
|
SET_MODEL(ENT(pev), "models/baby_headcrab.mdl"); |
|
pev->rendermode = kRenderTransTexture; |
|
pev->renderamt = 192; |
|
UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); |
|
|
|
pev->health = gSkillData.headcrabHealth * 0.25; // less health than full grown |
|
} |
|
|
|
void CBabyCrab :: Precache( void ) |
|
{ |
|
PRECACHE_MODEL( "models/baby_headcrab.mdl" ); |
|
CHeadCrab::Precache(); |
|
} |
|
|
|
|
|
void CBabyCrab :: SetYawSpeed ( void ) |
|
{ |
|
pev->yaw_speed = 120; |
|
} |
|
|
|
|
|
BOOL CBabyCrab :: CheckRangeAttack1( float flDot, float flDist ) |
|
{ |
|
if ( pev->flags & FL_ONGROUND ) |
|
{ |
|
if ( pev->groundentity && (pev->groundentity->v.flags & (FL_CLIENT|FL_MONSTER)) ) |
|
return TRUE; |
|
|
|
// A little less accurate, but jump from closer |
|
if ( flDist <= 180 && flDot >= 0.55 ) |
|
return TRUE; |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
|
|
Schedule_t* CBabyCrab :: GetScheduleOfType ( int Type ) |
|
{ |
|
switch( Type ) |
|
{ |
|
case SCHED_FAIL: // If you fail, try to jump! |
|
if ( m_hEnemy != NULL ) |
|
return slHCRangeAttack1Fast; |
|
break; |
|
|
|
case SCHED_RANGE_ATTACK1: |
|
{ |
|
return slHCRangeAttack1Fast; |
|
} |
|
break; |
|
} |
|
|
|
return CHeadCrab::GetScheduleOfType( Type ); |
|
}
|
|
|