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.
1012 lines
24 KiB
1012 lines
24 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
|
|
// events
|
|
//
|
|
// $Workfile: $
|
|
// $Date: $
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
// $Log: $
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "ai_default.h"
|
|
#include "ai_task.h"
|
|
#include "ai_schedule.h"
|
|
#include "ai_node.h"
|
|
#include "ai_hull.h"
|
|
#include "ai_hint.h"
|
|
#include "ai_memory.h"
|
|
#include "ai_route.h"
|
|
#include "ai_motor.h"
|
|
#include "ai_senses.h"
|
|
#include "soundent.h"
|
|
#include "game.h"
|
|
#include "npcevent.h"
|
|
#include "entitylist.h"
|
|
#include "activitylist.h"
|
|
#include "animation.h"
|
|
#include "basecombatweapon.h"
|
|
#include "IEffects.h"
|
|
#include "vstdlib/random.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "ammodef.h"
|
|
#include "hl1_ai_basenpc.h"
|
|
#include "studio.h" //hitbox parsing
|
|
#include "collisionutils.h" //ComputeSeparatingPlane
|
|
|
|
#include "physics_bone_follower.h" //For BoneFollowerManager
|
|
|
|
#define ACT_T_IDLE 1010
|
|
Activity ACT_1010;
|
|
Activity ACT_1011;
|
|
Activity ACT_1012;
|
|
Activity ACT_1013;
|
|
|
|
#define ACT_T_TAP 1020
|
|
Activity ACT_1020;
|
|
Activity ACT_1021;
|
|
Activity ACT_1022;
|
|
Activity ACT_1023;
|
|
|
|
#define ACT_T_STRIKE 1030
|
|
Activity ACT_1030;
|
|
Activity ACT_1031;
|
|
Activity ACT_1032;
|
|
Activity ACT_1033;
|
|
|
|
#define ACT_T_REARIDLE 1040
|
|
Activity ACT_1040;
|
|
Activity ACT_1041;
|
|
Activity ACT_1042;
|
|
Activity ACT_1043;
|
|
Activity ACT_1044;
|
|
|
|
class CNPC_Tentacle : public CHL1BaseNPC
|
|
{
|
|
DECLARE_CLASS( CNPC_Tentacle, CHL1BaseNPC );
|
|
public:
|
|
|
|
CNPC_Tentacle();
|
|
|
|
void Spawn( );
|
|
void Precache( );
|
|
bool KeyValue( const char *szKeyName, const char *szValue );
|
|
|
|
bool QueryHearSound( CSound *pSound ) { return true; } // Tentacle isn't picky.
|
|
|
|
|
|
int Level( float dz );
|
|
int MyLevel( void );
|
|
float MyHeight( void );
|
|
|
|
// Don't allow the tentacle to go across transitions!!!
|
|
virtual int ObjectCaps( void ) { return CAI_BaseNPC::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
|
|
|
|
void Start ( void );
|
|
void Cycle ( void );
|
|
void HitTouch( CBaseEntity *pOther );
|
|
|
|
void HandleAnimEvent( animevent_t *pEvent );
|
|
float HearingSensitivity( void ) { return 2.0; };
|
|
|
|
virtual int OnTakeDamage( const CTakeDamageInfo &info );
|
|
|
|
bool CreateVPhysics( void );
|
|
|
|
void UpdateOnRemove( void );
|
|
|
|
float m_flInitialYaw;
|
|
int m_iGoalAnim;
|
|
int m_iLevel;
|
|
int m_iDir;
|
|
float m_flFramerateAdj;
|
|
float m_flSoundYaw;
|
|
int m_iSoundLevel;
|
|
float m_flSoundTime;
|
|
float m_flSoundRadius;
|
|
int m_iHitDmg;
|
|
float m_flHitTime;
|
|
|
|
float m_flTapRadius;
|
|
|
|
float m_flNextSong;
|
|
static int g_fFlySound;
|
|
static int g_fSquirmSound;
|
|
|
|
float m_flMaxYaw;
|
|
int m_iTapSound;
|
|
|
|
Vector m_vecPrevSound;
|
|
float m_flPrevSoundTime;
|
|
|
|
float MaxYawSpeed( void ) { return 18.0f; }
|
|
|
|
bool HeardAnything( void );
|
|
|
|
Class_T Classify ( void );
|
|
|
|
CBoneFollowerManager m_BoneFollowerManager;
|
|
|
|
DECLARE_DATADESC();
|
|
DEFINE_CUSTOM_AI;
|
|
};
|
|
|
|
// Crane bones that have physics followers
|
|
static const char *pTentacleFollowerBoneNames[] =
|
|
{
|
|
"Bone08",
|
|
"Bone09"
|
|
};
|
|
|
|
int CNPC_Tentacle::g_fFlySound;
|
|
int CNPC_Tentacle::g_fSquirmSound;
|
|
|
|
LINK_ENTITY_TO_CLASS( monster_tentacle, CNPC_Tentacle );
|
|
|
|
// stike sounds
|
|
#define TE_NONE -1
|
|
#define TE_SILO 0
|
|
#define TE_DIRT 1
|
|
#define TE_WATER 2
|
|
|
|
// animation sequence aliases
|
|
typedef enum
|
|
{
|
|
TENTACLE_ANIM_Pit_Idle,
|
|
|
|
TENTACLE_ANIM_rise_to_Temp1,
|
|
TENTACLE_ANIM_Temp1_to_Floor,
|
|
TENTACLE_ANIM_Floor_Idle,
|
|
TENTACLE_ANIM_Floor_Fidget_Pissed,
|
|
TENTACLE_ANIM_Floor_Fidget_SmallRise,
|
|
TENTACLE_ANIM_Floor_Fidget_Wave,
|
|
TENTACLE_ANIM_Floor_Strike,
|
|
TENTACLE_ANIM_Floor_Tap,
|
|
TENTACLE_ANIM_Floor_Rotate,
|
|
TENTACLE_ANIM_Floor_Rear,
|
|
TENTACLE_ANIM_Floor_Rear_Idle,
|
|
TENTACLE_ANIM_Floor_to_Lev1,
|
|
|
|
TENTACLE_ANIM_Lev1_Idle,
|
|
TENTACLE_ANIM_Lev1_Fidget_Claw,
|
|
TENTACLE_ANIM_Lev1_Fidget_Shake,
|
|
TENTACLE_ANIM_Lev1_Fidget_Snap,
|
|
TENTACLE_ANIM_Lev1_Strike,
|
|
TENTACLE_ANIM_Lev1_Tap,
|
|
TENTACLE_ANIM_Lev1_Rotate,
|
|
TENTACLE_ANIM_Lev1_Rear,
|
|
TENTACLE_ANIM_Lev1_Rear_Idle,
|
|
TENTACLE_ANIM_Lev1_to_Lev2,
|
|
|
|
TENTACLE_ANIM_Lev2_Idle,
|
|
TENTACLE_ANIM_Lev2_Fidget_Shake,
|
|
TENTACLE_ANIM_Lev2_Fidget_Swing,
|
|
TENTACLE_ANIM_Lev2_Fidget_Tut,
|
|
TENTACLE_ANIM_Lev2_Strike,
|
|
TENTACLE_ANIM_Lev2_Tap,
|
|
TENTACLE_ANIM_Lev2_Rotate,
|
|
TENTACLE_ANIM_Lev2_Rear,
|
|
TENTACLE_ANIM_Lev2_Rear_Idle,
|
|
TENTACLE_ANIM_Lev2_to_Lev3,
|
|
|
|
TENTACLE_ANIM_Lev3_Idle,
|
|
TENTACLE_ANIM_Lev3_Fidget_Shake,
|
|
TENTACLE_ANIM_Lev3_Fidget_Side,
|
|
TENTACLE_ANIM_Lev3_Fidget_Swipe,
|
|
TENTACLE_ANIM_Lev3_Strike,
|
|
TENTACLE_ANIM_Lev3_Tap,
|
|
TENTACLE_ANIM_Lev3_Rotate,
|
|
TENTACLE_ANIM_Lev3_Rear,
|
|
TENTACLE_ANIM_Lev3_Rear_Idle,
|
|
|
|
TENTACLE_ANIM_Lev1_Door_reach,
|
|
|
|
TENTACLE_ANIM_Lev3_to_Engine,
|
|
TENTACLE_ANIM_Engine_Idle,
|
|
TENTACLE_ANIM_Engine_Sway,
|
|
TENTACLE_ANIM_Engine_Swat,
|
|
TENTACLE_ANIM_Engine_Bob,
|
|
TENTACLE_ANIM_Engine_Death1,
|
|
TENTACLE_ANIM_Engine_Death2,
|
|
TENTACLE_ANIM_Engine_Death3,
|
|
|
|
TENTACLE_ANIM_none
|
|
} TENTACLE_ANIM;
|
|
|
|
BEGIN_DATADESC( CNPC_Tentacle )
|
|
DEFINE_FIELD( m_flInitialYaw, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_iGoalAnim, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iLevel, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_iDir, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flFramerateAdj, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flSoundYaw, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_iSoundLevel, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flSoundTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flSoundRadius, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_iHitDmg, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flHitTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flTapRadius, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flNextSong, FIELD_TIME ),
|
|
DEFINE_FIELD( m_iTapSound, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flMaxYaw, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_vecPrevSound, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_flPrevSoundTime, FIELD_TIME ),
|
|
|
|
DEFINE_EMBEDDED( m_BoneFollowerManager ),
|
|
|
|
DEFINE_THINKFUNC( Start ),
|
|
DEFINE_THINKFUNC( Cycle ),
|
|
DEFINE_ENTITYFUNC( HitTouch ),
|
|
END_DATADESC()
|
|
|
|
Class_T CNPC_Tentacle::Classify ( void )
|
|
{
|
|
return CLASS_ALIEN_MONSTER;
|
|
}
|
|
|
|
|
|
CNPC_Tentacle::CNPC_Tentacle()
|
|
{
|
|
m_flMaxYaw = 65;
|
|
m_iTapSound = 0;
|
|
}
|
|
|
|
bool CNPC_Tentacle::KeyValue( const char *szKeyName, const char *szValue )
|
|
{
|
|
if ( FStrEq( szKeyName, "sweeparc") )
|
|
{
|
|
m_flMaxYaw = atof( szValue ) / 2.0;
|
|
return true;
|
|
}
|
|
else if (FStrEq( szKeyName, "sound"))
|
|
{
|
|
m_iTapSound = atoi( szValue );
|
|
return true;
|
|
}
|
|
else
|
|
return BaseClass::KeyValue( szKeyName, szValue );
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Tentacle Spawn
|
|
//
|
|
void CNPC_Tentacle::Spawn( )
|
|
{
|
|
Precache( );
|
|
|
|
SetSolid( SOLID_BBOX );
|
|
|
|
//Necessary for TestCollision to be called for hitbox ray hits
|
|
AddSolidFlags( FSOLID_CUSTOMRAYTEST );
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
ClearEffects();
|
|
m_iHealth = 75;
|
|
SetSequence( 0 );
|
|
|
|
SetModel( "models/tentacle2.mdl" );
|
|
UTIL_SetSize( this, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) );
|
|
|
|
// Use our hitboxes to determine our render bounds
|
|
CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
|
|
|
|
m_takedamage = DAMAGE_AIM;
|
|
AddFlag( FL_NPC );
|
|
|
|
m_bloodColor = BLOOD_COLOR_GREEN;
|
|
|
|
ResetSequenceInfo( );
|
|
m_iDir = 1;
|
|
|
|
SetThink( &CNPC_Tentacle::Start );
|
|
SetNextThink( gpGlobals->curtime + 0.2 );
|
|
|
|
SetTouch( &CNPC_Tentacle::HitTouch );
|
|
|
|
m_flInitialYaw = GetAbsAngles().y;
|
|
GetMotor()->SetIdealYawAndUpdate( m_flInitialYaw );
|
|
|
|
g_fFlySound = FALSE;
|
|
g_fSquirmSound = FALSE;
|
|
|
|
m_iHitDmg = 200;
|
|
|
|
if (m_flMaxYaw <= 0)
|
|
m_flMaxYaw = 65;
|
|
|
|
m_NPCState = NPC_STATE_IDLE;
|
|
|
|
UTIL_SetOrigin( this, GetAbsOrigin() );
|
|
|
|
CreateVPhysics();
|
|
|
|
AddEffects( EF_NOSHADOW );
|
|
}
|
|
|
|
void CNPC_Tentacle::UpdateOnRemove( void )
|
|
{
|
|
m_BoneFollowerManager.DestroyBoneFollowers();
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
void CNPC_Tentacle::Precache( )
|
|
{
|
|
PrecacheModel("models/tentacle2.mdl");
|
|
|
|
PrecacheScriptSound( "Tentacle.Flies" );
|
|
PrecacheScriptSound( "Tentacle.Squirm" );
|
|
PrecacheScriptSound( "Tentacle.Sing" );
|
|
PrecacheScriptSound( "Tentacle.HitDirt" );
|
|
PrecacheScriptSound( "Tentacle.Swing" );
|
|
PrecacheScriptSound( "Tentacle.Search" );
|
|
PrecacheScriptSound( "Tentacle.Roar" );
|
|
PrecacheScriptSound( "Tentacle.Alert" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
|
|
int CNPC_Tentacle::Level( float dz )
|
|
{
|
|
if (dz < 216)
|
|
return 0;
|
|
if (dz < 408)
|
|
return 1;
|
|
if (dz < 600)
|
|
return 2;
|
|
return 3;
|
|
}
|
|
|
|
|
|
float CNPC_Tentacle::MyHeight( )
|
|
{
|
|
switch ( MyLevel( ) )
|
|
{
|
|
case 1:
|
|
return 256;
|
|
case 2:
|
|
return 448;
|
|
case 3:
|
|
return 640;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int CNPC_Tentacle::MyLevel( )
|
|
{
|
|
switch( GetSequence() )
|
|
{
|
|
case TENTACLE_ANIM_Pit_Idle:
|
|
return -1;
|
|
|
|
case TENTACLE_ANIM_rise_to_Temp1:
|
|
case TENTACLE_ANIM_Temp1_to_Floor:
|
|
case TENTACLE_ANIM_Floor_to_Lev1:
|
|
return 0;
|
|
|
|
case TENTACLE_ANIM_Floor_Idle:
|
|
case TENTACLE_ANIM_Floor_Fidget_Pissed:
|
|
case TENTACLE_ANIM_Floor_Fidget_SmallRise:
|
|
case TENTACLE_ANIM_Floor_Fidget_Wave:
|
|
case TENTACLE_ANIM_Floor_Strike:
|
|
case TENTACLE_ANIM_Floor_Tap:
|
|
case TENTACLE_ANIM_Floor_Rotate:
|
|
case TENTACLE_ANIM_Floor_Rear:
|
|
case TENTACLE_ANIM_Floor_Rear_Idle:
|
|
return 0;
|
|
|
|
case TENTACLE_ANIM_Lev1_Idle:
|
|
case TENTACLE_ANIM_Lev1_Fidget_Claw:
|
|
case TENTACLE_ANIM_Lev1_Fidget_Shake:
|
|
case TENTACLE_ANIM_Lev1_Fidget_Snap:
|
|
case TENTACLE_ANIM_Lev1_Strike:
|
|
case TENTACLE_ANIM_Lev1_Tap:
|
|
case TENTACLE_ANIM_Lev1_Rotate:
|
|
case TENTACLE_ANIM_Lev1_Rear:
|
|
case TENTACLE_ANIM_Lev1_Rear_Idle:
|
|
return 1;
|
|
|
|
case TENTACLE_ANIM_Lev1_to_Lev2:
|
|
return 1;
|
|
|
|
case TENTACLE_ANIM_Lev2_Idle:
|
|
case TENTACLE_ANIM_Lev2_Fidget_Shake:
|
|
case TENTACLE_ANIM_Lev2_Fidget_Swing:
|
|
case TENTACLE_ANIM_Lev2_Fidget_Tut:
|
|
case TENTACLE_ANIM_Lev2_Strike:
|
|
case TENTACLE_ANIM_Lev2_Tap:
|
|
case TENTACLE_ANIM_Lev2_Rotate:
|
|
case TENTACLE_ANIM_Lev2_Rear:
|
|
case TENTACLE_ANIM_Lev2_Rear_Idle:
|
|
return 2;
|
|
|
|
case TENTACLE_ANIM_Lev2_to_Lev3:
|
|
return 2;
|
|
|
|
case TENTACLE_ANIM_Lev3_Idle:
|
|
case TENTACLE_ANIM_Lev3_Fidget_Shake:
|
|
case TENTACLE_ANIM_Lev3_Fidget_Side:
|
|
case TENTACLE_ANIM_Lev3_Fidget_Swipe:
|
|
case TENTACLE_ANIM_Lev3_Strike:
|
|
case TENTACLE_ANIM_Lev3_Tap:
|
|
case TENTACLE_ANIM_Lev3_Rotate:
|
|
case TENTACLE_ANIM_Lev3_Rear:
|
|
case TENTACLE_ANIM_Lev3_Rear_Idle:
|
|
return 3;
|
|
|
|
case TENTACLE_ANIM_Lev1_Door_reach:
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void CNPC_Tentacle::Start( void )
|
|
{
|
|
SetThink( &CNPC_Tentacle::Cycle );
|
|
|
|
CPASAttenuationFilter filter( this );
|
|
|
|
if ( !g_fFlySound )
|
|
{
|
|
EmitSound( filter, entindex(), "Tentacle.Flies" );
|
|
g_fFlySound = TRUE;
|
|
}
|
|
else if ( !g_fSquirmSound )
|
|
{
|
|
EmitSound( filter, entindex(), "Tentacle.Squirm" );
|
|
g_fSquirmSound = TRUE;
|
|
}
|
|
|
|
SetNextThink( gpGlobals->curtime + 0.1 );
|
|
}
|
|
|
|
bool CNPC_Tentacle::HeardAnything( void )
|
|
{
|
|
if ( HasCondition( COND_HEAR_DANGER ) || // I remove a bunch of sounds from here on purpose. Talk to me if you're changing this!(sjb)
|
|
HasCondition( COND_HEAR_COMBAT ) ||
|
|
HasCondition( COND_HEAR_WORLD ) ||
|
|
HasCondition( COND_HEAR_PLAYER ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void CNPC_Tentacle::Cycle( void )
|
|
{
|
|
//NDebugOverlay::Cross3D( EarPosition(), 32, 255, 0, 0, false, 0.1 );
|
|
|
|
// ALERT( at_console, "%s %.2f %d %d\n", STRING( pev->targetname ), pev->origin.z, m_MonsterState, m_IdealMonsterState );
|
|
SetNextThink( gpGlobals->curtime + 0.1 );
|
|
|
|
// ALERT( at_console, "%s %d %d %d %f %f\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim, m_iDir, pev->framerate, pev->health );
|
|
|
|
if ( m_NPCState == NPC_STATE_SCRIPT || GetIdealState() == NPC_STATE_SCRIPT)
|
|
{
|
|
SetAbsAngles( QAngle( GetAbsAngles().x, m_flInitialYaw, GetAbsAngles().z ) );
|
|
GetMotor()->SetIdealYaw( m_flInitialYaw );
|
|
RemoveIgnoredConditions();
|
|
NPCThink( );
|
|
m_iGoalAnim = TENTACLE_ANIM_Pit_Idle;
|
|
return;
|
|
}
|
|
|
|
StudioFrameAdvance();
|
|
DispatchAnimEvents( this );
|
|
|
|
GetMotor()->UpdateYaw( MaxYawSpeed() );
|
|
|
|
CSound *pSound = NULL;
|
|
|
|
GetSenses()->Listen();
|
|
m_BoneFollowerManager.UpdateBoneFollowers(this);
|
|
|
|
// Listen will set this if there's something in my sound list
|
|
if ( HeardAnything() )
|
|
pSound = GetSenses()->GetClosestSound( false, (SOUND_DANGER|SOUND_COMBAT|SOUND_WORLD|SOUND_PLAYER) );
|
|
else
|
|
pSound = NULL;
|
|
|
|
if ( pSound )
|
|
{
|
|
//NDebugOverlay::Line( EarPosition(), pSound->GetSoundOrigin(), 0, 255, 0, false, 0.2 );
|
|
|
|
Vector vecDir;
|
|
if ( gpGlobals->curtime - m_flPrevSoundTime < 0.5 )
|
|
{
|
|
float dt = gpGlobals->curtime - m_flPrevSoundTime;
|
|
vecDir = pSound->GetSoundOrigin() + (pSound->GetSoundOrigin() - m_vecPrevSound) / dt - GetAbsOrigin();
|
|
}
|
|
else
|
|
{
|
|
vecDir = pSound->GetSoundOrigin() - GetAbsOrigin();
|
|
}
|
|
|
|
m_flPrevSoundTime = gpGlobals->curtime;
|
|
m_vecPrevSound = pSound->GetSoundOrigin();
|
|
|
|
m_flSoundYaw = VecToYaw ( vecDir ) - m_flInitialYaw;
|
|
|
|
m_iSoundLevel = Level( vecDir.z );
|
|
|
|
if (m_flSoundYaw < -180)
|
|
m_flSoundYaw += 360;
|
|
if (m_flSoundYaw > 180)
|
|
m_flSoundYaw -= 360;
|
|
|
|
// ALERT( at_console, "sound %d %.0f\n", m_iSoundLevel, m_flSoundYaw );
|
|
if (m_flSoundTime < gpGlobals->curtime)
|
|
{
|
|
// play "I hear new something" sound
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin() + Vector( 0, 0, MyHeight()), "Tentacle.Alert", 1.0, SNDLVL_GUNFIRE, 0, 100);
|
|
}
|
|
m_flSoundTime = gpGlobals->curtime + random->RandomFloat( 5.0, 10.0 );
|
|
}
|
|
|
|
// clip ideal_yaw
|
|
float dy = m_flSoundYaw;
|
|
switch( GetSequence() )
|
|
{
|
|
case TENTACLE_ANIM_Floor_Rear:
|
|
case TENTACLE_ANIM_Floor_Rear_Idle:
|
|
case TENTACLE_ANIM_Lev1_Rear:
|
|
case TENTACLE_ANIM_Lev1_Rear_Idle:
|
|
case TENTACLE_ANIM_Lev2_Rear:
|
|
case TENTACLE_ANIM_Lev2_Rear_Idle:
|
|
case TENTACLE_ANIM_Lev3_Rear:
|
|
case TENTACLE_ANIM_Lev3_Rear_Idle:
|
|
if (dy < 0 && dy > -m_flMaxYaw)
|
|
dy = -m_flMaxYaw;
|
|
if (dy > 0 && dy < m_flMaxYaw)
|
|
dy = m_flMaxYaw;
|
|
break;
|
|
default:
|
|
if (dy < -m_flMaxYaw)
|
|
dy = -m_flMaxYaw;
|
|
if (dy > m_flMaxYaw)
|
|
dy = m_flMaxYaw;
|
|
}
|
|
GetMotor()->SetIdealYaw( m_flInitialYaw + dy );
|
|
|
|
if ( IsSequenceFinished() )
|
|
{
|
|
// ALERT( at_console, "%s done %d %d\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim );
|
|
if ( m_iHealth <= 1)
|
|
{
|
|
m_iGoalAnim = TENTACLE_ANIM_Pit_Idle;
|
|
|
|
if ( GetSequence() == TENTACLE_ANIM_Pit_Idle)
|
|
{
|
|
m_iHealth = 75;
|
|
}
|
|
}
|
|
else if ( m_flSoundTime > gpGlobals->curtime )
|
|
{
|
|
if (m_flSoundYaw >= -(m_flMaxYaw + 30) && m_flSoundYaw <= (m_flMaxYaw + 30))
|
|
{
|
|
// strike
|
|
switch ( m_iSoundLevel )
|
|
{
|
|
case 0:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1030 );
|
|
break;
|
|
case 1:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1031 );
|
|
break;
|
|
case 2:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1032 );
|
|
break;
|
|
case 3:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1033 );
|
|
break;
|
|
}
|
|
}
|
|
else if (m_flSoundYaw >= -m_flMaxYaw * 2 && m_flSoundYaw <= m_flMaxYaw * 2)
|
|
{
|
|
// tap
|
|
switch ( m_iSoundLevel )
|
|
{
|
|
case 0:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1020 );
|
|
break;
|
|
case 1:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1021 );
|
|
break;
|
|
case 2:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1022 );
|
|
break;
|
|
case 3:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1023 );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// go into rear idle
|
|
switch ( m_iSoundLevel )
|
|
{
|
|
case 0:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1040 );
|
|
break;
|
|
case 1:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1041 );
|
|
break;
|
|
case 2:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1042 );
|
|
break;
|
|
case 3:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1043 );
|
|
break;
|
|
case 4:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1044 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( GetSequence() == TENTACLE_ANIM_Pit_Idle)
|
|
{
|
|
// stay in pit until hear noise
|
|
m_iGoalAnim = TENTACLE_ANIM_Pit_Idle;
|
|
}
|
|
else if ( GetSequence() == m_iGoalAnim)
|
|
{
|
|
if ( MyLevel() >= 0 && gpGlobals->curtime < m_flSoundTime)
|
|
{
|
|
if ( random->RandomInt(0,9) < m_flSoundTime - gpGlobals->curtime )
|
|
{
|
|
// continue stike
|
|
switch ( m_iSoundLevel )
|
|
{
|
|
case 0:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1030 );
|
|
break;
|
|
case 1:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1031 );
|
|
break;
|
|
case 2:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1032 );
|
|
break;
|
|
case 3:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1033 );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// tap
|
|
switch ( m_iSoundLevel )
|
|
{
|
|
case 0:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1020 );
|
|
break;
|
|
case 1:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1021 );
|
|
break;
|
|
case 2:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1022 );
|
|
break;
|
|
case 3:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1023 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( MyLevel( ) < 0 )
|
|
{
|
|
m_iGoalAnim = SelectWeightedSequence( ACT_1010 );
|
|
}
|
|
else
|
|
{
|
|
if (m_flNextSong < gpGlobals->curtime)
|
|
{
|
|
// play "I hear new something" sound
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(), "Tentacle.Sing" );
|
|
|
|
m_flNextSong = gpGlobals->curtime + random->RandomFloat( 10, 20 );
|
|
}
|
|
|
|
if (random->RandomInt(0,15) == 0)
|
|
{
|
|
// idle on new level
|
|
switch ( random->RandomInt( 0, 3 ) )
|
|
{
|
|
case 0:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1010 );
|
|
break;
|
|
case 1:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1011 );
|
|
break;
|
|
case 2:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1012 );
|
|
break;
|
|
case 3:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1013 );
|
|
break;
|
|
}
|
|
}
|
|
else if ( random->RandomInt( 0, 3 ) == 0 )
|
|
{
|
|
// tap
|
|
switch ( MyLevel() )
|
|
{
|
|
case 0:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1020 );
|
|
break;
|
|
case 1:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1021 );
|
|
break;
|
|
case 2:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1022 );
|
|
break;
|
|
case 3:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1023 );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// idle
|
|
switch ( MyLevel() )
|
|
{
|
|
case 0:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1010 );
|
|
break;
|
|
case 1:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1011 );
|
|
break;
|
|
case 2:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1012 );
|
|
break;
|
|
case 3:
|
|
m_iGoalAnim = SelectWeightedSequence ( ACT_1013 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (m_flSoundYaw < 0)
|
|
m_flSoundYaw += random->RandomFloat( 2, 8 );
|
|
else
|
|
m_flSoundYaw -= random->RandomFloat( 2, 8 );
|
|
}
|
|
|
|
SetSequence( FindTransitionSequence( GetSequence(), m_iGoalAnim, &m_iDir ) );
|
|
|
|
|
|
if (m_iDir > 0)
|
|
{
|
|
SetCycle( 0 );
|
|
}
|
|
else
|
|
{
|
|
m_iDir = -1; // just to safe
|
|
SetCycle( 1.0f );
|
|
}
|
|
|
|
ResetSequenceInfo( );
|
|
|
|
m_flFramerateAdj = random->RandomFloat( -0.2, 0.2 );
|
|
m_flPlaybackRate = m_iDir * 1.0 + m_flFramerateAdj;
|
|
|
|
switch( GetSequence() )
|
|
{
|
|
case TENTACLE_ANIM_Floor_Tap:
|
|
case TENTACLE_ANIM_Lev1_Tap:
|
|
case TENTACLE_ANIM_Lev2_Tap:
|
|
case TENTACLE_ANIM_Lev3_Tap:
|
|
{
|
|
Vector vecSrc, v_forward;
|
|
AngleVectors( GetAbsAngles(), &v_forward );
|
|
|
|
trace_t tr1, tr2;
|
|
|
|
vecSrc = GetAbsOrigin() + Vector( 0, 0, MyHeight() - 4);
|
|
UTIL_TraceLine( vecSrc, vecSrc + v_forward * 512, MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr1 );
|
|
|
|
vecSrc = GetAbsOrigin() + Vector( 0, 0, MyHeight() + 8);
|
|
UTIL_TraceLine( vecSrc, vecSrc + v_forward * 512, MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr2 );
|
|
|
|
// ALERT( at_console, "%f %f\n", tr1.flFraction * 512, tr2.flFraction * 512 );
|
|
|
|
m_flTapRadius = SetPoseParameter( 0, random->RandomFloat( tr1.fraction * 512, tr2.fraction * 512 ) );
|
|
}
|
|
break;
|
|
default:
|
|
m_flTapRadius = 336; // 400 - 64
|
|
break;
|
|
}
|
|
SetViewOffset( Vector( 0, 0, MyHeight() ) );
|
|
// ALERT( at_console, "seq %d\n", pev->sequence );
|
|
}
|
|
|
|
if (m_flPrevSoundTime + 2.0 > gpGlobals->curtime)
|
|
{
|
|
// 1.5 normal speed if hears sounds
|
|
m_flPlaybackRate = m_iDir * 1.5 + m_flFramerateAdj;
|
|
}
|
|
else if (m_flPrevSoundTime + 5.0 > gpGlobals->curtime)
|
|
{
|
|
// slowdown to normal
|
|
m_flPlaybackRate = m_iDir + m_iDir * (5 - (gpGlobals->curtime - m_flPrevSoundTime)) / 2 + m_flFramerateAdj;
|
|
}
|
|
}
|
|
|
|
void CNPC_Tentacle::HandleAnimEvent( animevent_t *pEvent )
|
|
{
|
|
switch( pEvent->event )
|
|
{
|
|
case 1: // bang
|
|
{
|
|
Vector vecSrc;
|
|
QAngle angAngles;
|
|
GetAttachment( "0", vecSrc, angAngles );
|
|
|
|
// Vector vecSrc = GetAbsOrigin() + m_flTapRadius * Vector( cos( GetAbsAngles().y * (3.14192653 / 180.0) ), sin( GetAbsAngles().y * (M_PI / 180.0) ), 0.0 );
|
|
|
|
// vecSrc.z += MyHeight( );
|
|
|
|
switch( m_iTapSound )
|
|
{
|
|
case TE_SILO:
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), vecSrc, "Tentacle.HitSilo", 1.0, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
case TE_NONE:
|
|
break;
|
|
case TE_DIRT:
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), vecSrc, "Tentacle.HitDirt", 1.0, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
case TE_WATER:
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), vecSrc, "Tentacle.HitWater", 1.0, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 3: // start killing swing
|
|
m_iHitDmg = 200;
|
|
break;
|
|
|
|
case 4: // end killing swing
|
|
m_iHitDmg = 25;
|
|
break;
|
|
|
|
case 5: // just "whoosh" sound
|
|
break;
|
|
|
|
case 2: // tap scrape
|
|
case 6: // light tap
|
|
{
|
|
Vector vecSrc = GetAbsOrigin() + m_flTapRadius * Vector( cos( GetAbsAngles().y * (M_PI / 180.0) ), sin( GetAbsAngles().y * (M_PI / 180.0) ), 0.0 );
|
|
|
|
vecSrc.z += MyHeight( );
|
|
|
|
float flVol = random->RandomFloat( 0.3, 0.5 );
|
|
|
|
switch( m_iTapSound )
|
|
{
|
|
case TE_SILO:
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), vecSrc, "Tentacle.HitSilo", flVol, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
case TE_NONE:
|
|
break;
|
|
case TE_DIRT:
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), vecSrc, "Tentacle.HitDirt", flVol, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
case TE_WATER:
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), vecSrc, "Tentacle.HitWater", flVol, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case 7: // roar
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin() + Vector( 0, 0, MyHeight()), "Tentacle.Roar", 1.0, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
|
|
case 8: // search
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin() + Vector( 0, 0, MyHeight()), "Tentacle.Search", 1.0, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
|
|
case 9: // swing
|
|
UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin() + Vector( 0, 0, MyHeight()), "Tentacle.Swing", 1.0, SNDLVL_GUNFIRE, 0, 100);
|
|
break;
|
|
default:
|
|
BaseClass::HandleAnimEvent( pEvent );
|
|
}
|
|
}
|
|
|
|
void CNPC_Tentacle::HitTouch( CBaseEntity *pOther )
|
|
{
|
|
if (m_flHitTime > gpGlobals->curtime)
|
|
return;
|
|
|
|
// only look at the ones where the player hit me
|
|
if( pOther == NULL || pOther->GetModelIndex() == GetModelIndex() || ( pOther->GetSolidFlags() & FSOLID_TRIGGER ) )
|
|
return;
|
|
|
|
//Right now the BoneFollower will always be hit in box 0, and
|
|
//will pass that to us. Make *any* touch by the physics objects a kill
|
|
//as the ragdoll only covers the top portion of the tentacle.
|
|
if ( pOther->m_takedamage )
|
|
{
|
|
CTakeDamageInfo info( this, this, m_iHitDmg, DMG_CLUB );
|
|
|
|
Vector vDamageForce = pOther->GetAbsOrigin() - GetAbsOrigin();
|
|
VectorNormalize( vDamageForce );
|
|
|
|
CalculateMeleeDamageForce( &info, vDamageForce, pOther->GetAbsOrigin() );
|
|
pOther->TakeDamage( info );
|
|
|
|
m_flHitTime = gpGlobals->curtime + 0.5;
|
|
}
|
|
}
|
|
|
|
int CNPC_Tentacle::OnTakeDamage( const CTakeDamageInfo &info )
|
|
{
|
|
CTakeDamageInfo i = info;
|
|
|
|
//Don't allow the tentacle to die. Instead set health to 1, so we can do our own death and rebirth
|
|
if( (int)i.GetDamage() >= m_iHealth )
|
|
{
|
|
i.SetDamage( 0.0f );
|
|
m_iHealth = 1;
|
|
}
|
|
|
|
return BaseClass::OnTakeDamage( i );
|
|
}
|
|
|
|
bool CNPC_Tentacle::CreateVPhysics( void )
|
|
{
|
|
BaseClass::CreateVPhysics();
|
|
|
|
IPhysicsObject *pPhysics = VPhysicsGetObject();
|
|
if( pPhysics )
|
|
{
|
|
unsigned short flags = pPhysics->GetCallbackFlags();
|
|
|
|
flags |= CALLBACK_GLOBAL_TOUCH;
|
|
|
|
pPhysics->SetCallbackFlags( flags );
|
|
}
|
|
m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pTentacleFollowerBoneNames), pTentacleFollowerBoneNames );
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Schedules
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
AI_BEGIN_CUSTOM_NPC( monster_tentacle, CNPC_Tentacle )
|
|
|
|
DECLARE_ACTIVITY( ACT_1010 )
|
|
DECLARE_ACTIVITY( ACT_1011 )
|
|
DECLARE_ACTIVITY( ACT_1012 )
|
|
DECLARE_ACTIVITY( ACT_1013 )
|
|
|
|
DECLARE_ACTIVITY( ACT_1020 )
|
|
DECLARE_ACTIVITY( ACT_1021 )
|
|
DECLARE_ACTIVITY( ACT_1022 )
|
|
DECLARE_ACTIVITY( ACT_1023 )
|
|
|
|
DECLARE_ACTIVITY( ACT_1030 )
|
|
DECLARE_ACTIVITY( ACT_1031 )
|
|
DECLARE_ACTIVITY( ACT_1032 )
|
|
DECLARE_ACTIVITY( ACT_1033 )
|
|
|
|
DECLARE_ACTIVITY( ACT_1040 )
|
|
DECLARE_ACTIVITY( ACT_1041 )
|
|
DECLARE_ACTIVITY( ACT_1042 )
|
|
DECLARE_ACTIVITY( ACT_1043 )
|
|
DECLARE_ACTIVITY( ACT_1044 )
|
|
|
|
AI_END_CUSTOM_NPC()
|