source-engine/game/server/hl1/hl1_npc_tentacle.cpp
2022-04-16 12:05:19 +03:00

1013 lines
24 KiB
C++

//========= 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()