summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_npc_nihilanth.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl1/hl1_npc_nihilanth.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/hl1/hl1_npc_nihilanth.cpp')
-rw-r--r--game/server/hl1/hl1_npc_nihilanth.cpp1745
1 files changed, 1745 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_nihilanth.cpp b/game/server/hl1/hl1_npc_nihilanth.cpp
new file mode 100644
index 0000000..a045a27
--- /dev/null
+++ b/game/server/hl1/hl1_npc_nihilanth.cpp
@@ -0,0 +1,1745 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $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 "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 "Sprite.h"
+#include "hl1_ai_basenpc.h"
+#include "ai_senses.h"
+#include "Sprite.h"
+#include "beam_shared.h"
+#include "logicrelay.h"
+#include "ai_navigator.h"
+
+
+#define N_SCALE 15
+#define N_SPHERES 20
+
+ConVar sk_nihilanth_health( "sk_nihilanth_health", "800" );
+ConVar sk_nihilanth_zap( "sk_nihilanth_zap", "30" );
+
+class CNPC_Nihilanth : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Nihilanth, CHL1BaseNPC );
+public:
+ void Spawn( void );
+ void Precache( void );
+
+ Class_T Classify( void ) { return CLASS_ALIEN_MILITARY; };
+
+ /* void Killed( entvars_t *pevAttacker, int iGib );
+ void GibMonster( void );
+
+ void TargetSphere( USE_TYPE useType, float value );
+ CBaseEntity *RandomTargetname( const char *szName );
+ void MakeFriend( Vector vecPos );
+
+ int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType );
+ void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType);
+ */
+
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ bool ShouldGib( const CTakeDamageInfo &info ) { return false; }
+
+ void PainSound( const CTakeDamageInfo &info );
+ void DeathSound( const CTakeDamageInfo &info );
+
+ void StartupThink( void );
+ void NullThink( void );
+
+ void HuntThink( void );
+ void DyingThink( void );
+
+ void Flight( void );
+ void NextActivity( void );
+ void FloatSequence( void );
+ void HandleAnimEvent( animevent_t *pEvent );
+ bool EmitSphere( void );
+
+ void ShootBalls( void );
+ bool AbsorbSphere( void );
+
+ void MakeFriend( Vector vecStart );
+
+ void InputTurnBabyOn( inputdata_t &inputdata );
+ void InputTurnBabyOff( inputdata_t &inputdata );
+
+ float m_flForce;
+
+ float m_flNextPainSound;
+
+ Vector m_velocity;
+ Vector m_avelocity;
+
+ Vector m_vecTarget;
+ Vector m_posTarget;
+
+ Vector m_vecDesired;
+ Vector m_posDesired;
+
+ float m_flMinZ;
+ float m_flMaxZ;
+
+ Vector m_vecGoal;
+
+ float m_flLastSeen;
+ float m_flPrevSeen;
+
+ int m_irritation;
+
+ int m_iLevel;
+ int m_iTeleport;
+
+ EHANDLE m_hRecharger;
+
+ EHANDLE m_hSphere[N_SPHERES];
+ int m_iActiveSpheres;
+
+ float m_flAdj;
+
+ CSprite *m_pBall;
+
+ char m_szRechargerTarget[64];
+ char m_szDrawUse[64];
+ char m_szTeleportUse[64];
+ char m_szTeleportTouch[64];
+ char m_szDeadUse[64];
+ char m_szDeadTouch[64];
+
+ float m_flShootEnd;
+ float m_flShootTime;
+
+ EHANDLE m_hFriend[3];
+
+ bool m_bDead;
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( monster_nihilanth, CNPC_Nihilanth );
+
+BEGIN_DATADESC( CNPC_Nihilanth )
+ DEFINE_FIELD( m_flForce, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextPainSound, FIELD_TIME ),
+ DEFINE_FIELD( m_velocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_avelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecTarget, FIELD_VECTOR ),
+ DEFINE_FIELD( m_posTarget, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecDesired, FIELD_VECTOR ),
+ DEFINE_FIELD( m_posDesired, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flMinZ, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flMaxZ, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecGoal, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flLastSeen, FIELD_TIME ),
+ DEFINE_FIELD( m_flPrevSeen, FIELD_TIME ),
+ DEFINE_FIELD( m_irritation, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iLevel, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iTeleport, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hRecharger, FIELD_EHANDLE ),
+ DEFINE_ARRAY( m_hSphere, FIELD_EHANDLE, N_SPHERES ),
+ DEFINE_FIELD( m_iActiveSpheres, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flAdj, FIELD_FLOAT ),
+ DEFINE_FIELD( m_pBall, FIELD_CLASSPTR ),
+ DEFINE_ARRAY( m_szRechargerTarget, FIELD_CHARACTER, 64 ),
+ DEFINE_ARRAY( m_szDrawUse, FIELD_CHARACTER, 64 ),
+ DEFINE_ARRAY( m_szTeleportUse, FIELD_CHARACTER, 64 ),
+ DEFINE_ARRAY( m_szTeleportTouch, FIELD_CHARACTER, 64 ),
+ DEFINE_ARRAY( m_szDeadUse, FIELD_CHARACTER, 64 ),
+ DEFINE_ARRAY( m_szDeadTouch, FIELD_CHARACTER, 64 ),
+ DEFINE_FIELD( m_flShootEnd, FIELD_TIME ),
+ DEFINE_FIELD( m_flShootTime, FIELD_TIME ),
+ DEFINE_ARRAY( m_hFriend, FIELD_EHANDLE, 3 ),
+ DEFINE_FIELD( m_bDead, FIELD_BOOLEAN ),
+ DEFINE_THINKFUNC( NullThink ),
+ DEFINE_THINKFUNC( StartupThink ),
+ DEFINE_THINKFUNC( HuntThink ),
+ DEFINE_THINKFUNC( DyingThink ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "TurnBabyOn", InputTurnBabyOn ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "TurnBabyOff", InputTurnBabyOff ),
+
+END_DATADESC()
+
+class CNihilanthHVR : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNihilanthHVR, CAI_BaseNPC );
+public:
+ void Spawn( void );
+ void Precache( void );
+
+ void CircleInit( CBaseEntity *pTarget );
+ void AbsorbInit( void );
+ void GreenBallInit( void );
+
+
+ void RemoveTouch( CBaseEntity *pOther );
+
+ /*void Zap( void );
+ void Teleport( void );*/
+
+ void HoverThink( void );
+ bool CircleTarget( Vector vecTarget );
+ void BounceTouch( CBaseEntity *pOther );
+
+ void ZapThink( void );
+ void ZapInit( CBaseEntity *pEnemy );
+ void ZapTouch( CBaseEntity *pOther );
+
+ void TeleportThink( void );
+ void TeleportTouch( CBaseEntity *pOther );
+
+ void MovetoTarget( Vector vecTarget );
+
+ void DissipateThink( void );
+
+ CSprite *SpriteInit( const char *pSpriteName, CNihilanthHVR *pOwner );
+
+ void TeleportInit( CNPC_Nihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch );
+
+ float m_flIdealVel;
+ Vector m_vecIdeal;
+ CNPC_Nihilanth *m_pNihilanth;
+ EHANDLE m_hTouch;
+
+
+ float m_flBallScale;
+
+ void SetSprite( CBaseEntity *pSprite )
+ {
+ m_hSprite = pSprite;
+ }
+
+ CBaseEntity *GetSprite( void )
+ {
+ return m_hSprite.Get();
+ }
+
+ void SetBeam( CBaseEntity *pBeam )
+ {
+ m_hBeam = pBeam;
+ }
+
+ CBaseEntity *GetBeam( void )
+ {
+ return m_hBeam.Get();
+ }
+
+private:
+
+ EHANDLE m_hSprite;
+ EHANDLE m_hBeam;
+
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( nihilanth_energy_ball, CNihilanthHVR );
+
+
+BEGIN_DATADESC( CNihilanthHVR )
+ DEFINE_FIELD( m_flIdealVel, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flBallScale, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecIdeal, FIELD_VECTOR ),
+ DEFINE_FIELD( m_pNihilanth, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_hTouch, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hBeam, FIELD_EHANDLE ),
+
+ DEFINE_THINKFUNC( HoverThink ),
+ DEFINE_ENTITYFUNC( BounceTouch ),
+ DEFINE_THINKFUNC( ZapThink ),
+ DEFINE_ENTITYFUNC( ZapTouch ),
+ DEFINE_THINKFUNC( DissipateThink ),
+ DEFINE_THINKFUNC( TeleportThink ),
+ DEFINE_ENTITYFUNC( TeleportTouch ),
+ DEFINE_ENTITYFUNC( RemoveTouch ),
+END_DATADESC()
+
+
+//=========================================================
+// Nihilanth, final Boss monster
+//=========================================================
+
+void CNPC_Nihilanth::Spawn( void )
+{
+ Precache( );
+ // motor
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+
+ SetModel( "models/nihilanth.mdl" );
+ //UTIL_SetSize( this, Vector( -300, -300, 0), Vector(300, 300, 512));
+ //UTIL_SetSize(this, Vector( -32, -32, 0), Vector(32, 32, 64 ));
+
+ UTIL_SetSize(this, Vector( -16 * N_SCALE, -16 * N_SCALE, -48 * N_SCALE ), Vector( 16 * N_SCALE, 16 * N_SCALE, 28 * N_SCALE ) );
+
+ Vector vecSurroundingMins( -16 * N_SCALE, -16 * N_SCALE, -48 * N_SCALE );
+ Vector vecSurroundingMaxs( 16 * N_SCALE, 16 * N_SCALE, 28 * N_SCALE );
+ CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );
+
+ UTIL_SetOrigin( this, GetAbsOrigin() - Vector( 0, 0, 64 ) );
+
+ AddFlag( FL_NPC );
+ m_takedamage = DAMAGE_AIM;
+ m_iHealth = sk_nihilanth_health.GetFloat();
+ SetViewOffset ( Vector( 0, 0, 300 ) );
+
+ m_flFieldOfView = -1; // 360 degrees
+
+ SetSequence( 0 );
+ ResetSequenceInfo( );
+
+ InitBoneControllers();
+
+ SetThink( &CNPC_Nihilanth::StartupThink );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ m_vecDesired = Vector( 1, 0, 0 );
+ m_posDesired = Vector( GetAbsOrigin().x, GetAbsOrigin().y, 512 );
+
+ m_iLevel = 1;
+ m_iTeleport = 1;
+
+ if (m_szRechargerTarget[0] == '\0') Q_strncpy( m_szRechargerTarget, "n_recharger", sizeof( m_szRechargerTarget ) );
+ if (m_szDrawUse[0] == '\0') Q_strncpy( m_szDrawUse, "n_draw", sizeof( m_szDrawUse ) );
+ if (m_szTeleportUse[0] == '\0') Q_strncpy( m_szTeleportUse, "n_leaving", sizeof( m_szTeleportUse ) );
+ if (m_szTeleportTouch[0] == '\0') Q_strncpy( m_szTeleportTouch, "n_teleport", sizeof( m_szTeleportTouch ) );
+ if (m_szDeadUse[0] == '\0') Q_strncpy( m_szDeadUse, "n_dead", sizeof( m_szDeadUse ) );
+ if (m_szDeadTouch[0] == '\0') Q_strncpy( m_szDeadTouch, "n_ending", sizeof( m_szDeadTouch ) );
+
+ SetBloodColor( BLOOD_COLOR_YELLOW );
+}
+
+
+void CNPC_Nihilanth::Precache( void )
+{
+ PrecacheModel("models/nihilanth.mdl");
+ PrecacheModel("sprites/lgtning.vmt");
+ UTIL_PrecacheOther( "nihilanth_energy_ball" );
+ UTIL_PrecacheOther( "monster_alien_controller" );
+ UTIL_PrecacheOther( "monster_alien_slave" );
+
+ PrecacheScriptSound( "Nihilanth.PainLaugh" );
+ PrecacheScriptSound( "Nihilanth.Pain" );
+ PrecacheScriptSound( "Nihilanth.Die" );
+ PrecacheScriptSound( "Nihilanth.FriendBeam" );
+ PrecacheScriptSound( "Nihilanth.Attack" );
+ PrecacheScriptSound( "Nihilanth.BallAttack" );
+ PrecacheScriptSound( "Nihilanth.Recharge" );
+
+}
+
+void CNPC_Nihilanth::PainSound( const CTakeDamageInfo &info )
+{
+ if (m_flNextPainSound > gpGlobals->curtime)
+ return;
+
+ m_flNextPainSound = gpGlobals->curtime + random->RandomFloat( 2, 5 );
+
+ if ( m_iHealth > sk_nihilanth_health.GetFloat() / 2 )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Nihilanth.PainLaugh" );
+ }
+ else if (m_irritation >= 2)
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Nihilanth.Pain" );
+ }
+}
+
+void CNPC_Nihilanth::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Nihilanth.Die" );
+}
+
+int CNPC_Nihilanth::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ if ( info.GetInflictor() == this )
+ return 0;
+
+ if ( m_bDead )
+ return 0;
+
+ if ( info.GetDamage() >= m_iHealth )
+ {
+ m_iHealth = 1;
+ if ( m_irritation != 3 )
+ return 0;
+ }
+
+ PainSound( info );
+
+ m_iHealth -= info.GetDamage();
+
+ if( m_iHealth < 0 )
+ {
+ m_iHealth = 1;
+ m_bDead = true;
+ m_takedamage = DAMAGE_NO;
+ }
+
+ return 0;
+}
+
+void CNPC_Nihilanth::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ if (m_irritation == 3)
+ m_irritation = 2;
+
+ if (m_irritation == 2 && ptr->hitgroup == 0 && info.GetDamage() > 2)
+ m_irritation = 3;
+
+ if (m_irritation != 3)
+ {
+ Vector vecBlood = (ptr->endpos - GetAbsOrigin() );
+
+ VectorNormalize( vecBlood );
+
+ UTIL_BloodStream( ptr->endpos, vecBlood, BloodColor(), info.GetDamage() + (100 - 100 * (m_iHealth / sk_nihilanth_health.GetFloat() )));
+ }
+
+ // SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 5.0);// a little surface blood.
+ AddMultiDamage( info, this );
+}
+
+bool CNPC_Nihilanth::EmitSphere( void )
+{
+ m_iActiveSpheres = 0;
+ int empty = 0;
+
+ for (int i = 0; i < N_SPHERES; i++)
+ {
+ if (m_hSphere[i] != NULL)
+ {
+ m_iActiveSpheres++;
+ }
+ else
+ {
+ empty = i;
+ }
+ }
+
+ if (m_iActiveSpheres >= N_SPHERES)
+ return false;
+
+ Vector vecSrc = m_hRecharger->GetAbsOrigin();
+ CNihilanthHVR *pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR, "nihilanth_energy_ball" );
+
+
+ pEntity->SetAbsOrigin( vecSrc );
+ pEntity->SetAbsAngles( GetAbsAngles() );
+ pEntity->SetOwnerEntity( this );
+ pEntity->Spawn();
+
+ pEntity->SetAbsVelocity( GetAbsOrigin() - vecSrc );
+ pEntity->CircleInit( this );
+ m_hSphere[empty] = pEntity;
+
+ return true;
+}
+
+void CNPC_Nihilanth::NullThink( void )
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.5 );
+}
+
+void CNPC_Nihilanth::StartupThink( void )
+{
+ m_irritation = 0;
+ m_flAdj = 512;
+
+ CBaseEntity *pEntity;
+
+ pEntity = gEntList.FindEntityByName( NULL, "n_min" );
+ if (pEntity)
+ m_flMinZ = pEntity->GetAbsOrigin().z;
+ else
+ m_flMinZ = -4096;
+
+ pEntity = gEntList.FindEntityByName( NULL, "n_max" );
+ if (pEntity)
+ m_flMaxZ = pEntity->GetAbsOrigin().z;
+ else
+ m_flMaxZ = 4096;
+
+ m_hRecharger = this;
+
+ //TODO
+ for (int i = 0; i < N_SPHERES; i++)
+ {
+ EmitSphere();
+ }
+
+ m_hRecharger = NULL;
+
+ SetUse( NULL );
+ SetThink( &CNPC_Nihilanth::HuntThink);
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+}
+
+void CNPC_Nihilanth::InputTurnBabyOn( inputdata_t &inputdata )
+{
+ if ( m_irritation == 0 )
+ {
+ m_irritation = 1;
+ }
+}
+
+void CNPC_Nihilanth::InputTurnBabyOff( inputdata_t &inputdata )
+{
+ CBaseEntity *pTouch = gEntList.FindEntityByName( NULL, m_szDeadTouch );
+
+ if ( pTouch && GetEnemy() != NULL )
+ pTouch->Touch( GetEnemy() );
+}
+
+bool CNPC_Nihilanth::AbsorbSphere( void )
+{
+ for (int i = 0; i < N_SPHERES; i++)
+ {
+ if (m_hSphere[i] != NULL)
+ {
+ CNihilanthHVR *pSphere = (CNihilanthHVR *)m_hSphere[i].Get();
+ pSphere->AbsorbInit();
+ m_hSphere[i] = NULL;
+ m_iActiveSpheres--;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+void CNPC_Nihilanth::HuntThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ DispatchAnimEvents( this );
+ StudioFrameAdvance( );
+
+ ShootBalls();
+
+ // if dead, force cancelation of current animation
+ if ( m_bDead )
+ {
+ SetThink( &CNPC_Nihilanth::DyingThink );
+ SetCycle( 1.0f );
+
+ StudioFrameAdvance();
+ return;
+ }
+
+ // ALERT( at_console, "health %.0f\n", pev->health );
+
+ // if damaged, try to abosorb some spheres
+ if ( m_iHealth < sk_nihilanth_health.GetFloat() && AbsorbSphere() )
+ {
+ m_iHealth += sk_nihilanth_health.GetFloat() / N_SPHERES;
+ }
+
+ // get new sequence
+ if ( IsSequenceFinished() )
+ {
+ SetCycle( 0 );
+
+ NextActivity( );
+ ResetSequenceInfo( );
+ m_flPlaybackRate = 2.0 - 1.0 * ( m_iHealth / sk_nihilanth_health.GetFloat() );
+ }
+
+ // look for current enemy
+ if ( GetEnemy() != NULL && m_hRecharger == NULL)
+ {
+ if (FVisible( GetEnemy() ))
+ {
+ if (m_flLastSeen < gpGlobals->curtime - 5)
+ m_flPrevSeen = gpGlobals->curtime;
+
+ m_flLastSeen = gpGlobals->curtime;
+ m_posTarget = GetEnemy()->GetAbsOrigin();
+ m_vecTarget = m_posTarget - GetAbsOrigin();
+
+ VectorNormalize( m_vecTarget );
+
+ m_vecDesired = m_vecTarget;
+ m_posDesired = Vector( GetAbsOrigin().x, GetAbsOrigin().y, m_posTarget.z + m_flAdj );
+ }
+ else
+ {
+ m_flAdj = MIN( m_flAdj + 10, 1000 );
+ }
+ }
+
+ // don't go too high
+ if (m_posDesired.z > m_flMaxZ)
+ m_posDesired.z = m_flMaxZ;
+
+ // don't go too low
+ if (m_posDesired.z < m_flMinZ)
+ m_posDesired.z = m_flMinZ;
+
+ Flight( );
+}
+
+void CNPC_Nihilanth::Flight( void )
+{
+ Vector vForward, vRight, vUp;
+
+ QAngle vAngle = QAngle( GetAbsAngles().x + m_avelocity.x, GetAbsAngles().y + m_avelocity.y, GetAbsAngles().z + m_avelocity.z );
+
+ AngleVectors( vAngle, &vForward, &vRight, &vUp );
+ float flSide = DotProduct( m_vecDesired, vRight );
+
+ if (flSide < 0)
+ {
+ if (m_avelocity.y < 180)
+ {
+ m_avelocity.y += 6; // 9 * (3.0/2.0);
+ }
+ }
+ else
+ {
+ if (m_avelocity.y > -180)
+ {
+ m_avelocity.y -= 6; // 9 * (3.0/2.0);
+ }
+ }
+ m_avelocity.y *= 0.98;
+
+ // estimate where I'll be in two seconds
+ Vector vecEst = GetAbsOrigin() + m_velocity * 2.0 + vUp * m_flForce * 20;
+
+ // add immediate force
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ m_velocity.x += vUp.x * m_flForce;
+ m_velocity.y += vUp.y * m_flForce;
+ m_velocity.z += vUp.z * m_flForce;
+
+
+ float flSpeed = m_velocity.Length();
+ float flDir = DotProduct( Vector( vForward.x, vForward.y, 0 ), Vector( m_velocity.x, m_velocity.y, 0 ) );
+ if (flDir < 0)
+ flSpeed = -flSpeed;
+
+ // sideways drag
+ m_velocity.x = m_velocity.x * (1.0 - fabs( vRight.x ) * 0.05);
+ m_velocity.y = m_velocity.y * (1.0 - fabs( vRight.y ) * 0.05);
+ m_velocity.z = m_velocity.z * (1.0 - fabs( vRight.z ) * 0.05);
+
+ // general drag
+ m_velocity = m_velocity * 0.995;
+
+ // apply power to stay correct height
+ if (m_flForce < 100 && vecEst.z < m_posDesired.z)
+ {
+ m_flForce += 10;
+ }
+ else if (m_flForce > -100 && vecEst.z > m_posDesired.z)
+ {
+ if (vecEst.z > m_posDesired.z)
+ m_flForce -= 10;
+ }
+
+ SetAbsVelocity( m_velocity );
+
+ vAngle = QAngle( GetAbsAngles().x + m_avelocity.x * 0.1, GetAbsAngles().y + m_avelocity.y * 0.1, GetAbsAngles().z + m_avelocity.z * 0.1 );
+
+ SetAbsAngles( vAngle );
+
+ // ALERT( at_console, "%5.0f %5.0f : %4.0f : %3.0f : %2.0f\n", m_posDesired.z, pev->origin.z, m_velocity.z, m_avelocity.y, m_flForce );
+}
+
+void CNPC_Nihilanth::NextActivity( )
+{
+ Vector vForward, vRight, vUp;
+
+ SetIdealActivity( ACT_DO_NOT_DISTURB );
+
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ if (m_irritation >= 2)
+ {
+ if (m_pBall == NULL)
+ {
+ m_pBall = CSprite::SpriteCreate( "sprites/tele1.vmt", GetAbsOrigin(), true );
+ if (m_pBall)
+ {
+ m_pBall->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_pBall->SetAttachment( this, 1 );
+ m_pBall->SetScale( 4.0 );
+ m_pBall->m_flSpriteFramerate = 10.0f;
+ m_pBall->TurnOn( );
+ }
+ }
+
+ if (m_pBall)
+ {
+ CBroadcastRecipientFilter filterlight;
+ Vector vOrigin;
+ QAngle vAngle;
+
+ GetAttachment( 2, vOrigin, vAngle );
+
+ te->DynamicLight( filterlight, 0.0, &vOrigin, 255, 192, 64, 0, 256, 20, 0 );
+ }
+ }
+
+
+ if (( m_iHealth < sk_nihilanth_health.GetFloat() / 2 || m_iActiveSpheres < N_SPHERES / 2) && m_hRecharger == NULL && m_iLevel <= 9)
+ {
+ char szName[64];
+
+ CBaseEntity *pEnt = NULL;
+ CBaseEntity *pRecharger = NULL;
+ float flDist = 8192;
+
+ Q_snprintf(szName, sizeof( szName ), "%s%d", m_szRechargerTarget, m_iLevel );
+
+ while ((pEnt = gEntList.FindEntityByName( pEnt, szName )) != NULL )
+ {
+ float flLocal = (pEnt->GetAbsOrigin() - GetAbsOrigin() ).Length();
+
+ if ( flLocal < flDist )
+ {
+ flDist = flLocal;
+ pRecharger = pEnt;
+ }
+ }
+
+ if (pRecharger)
+ {
+ m_hRecharger = pRecharger;
+ m_posDesired = Vector( GetAbsOrigin().x, GetAbsOrigin().y, pRecharger->GetAbsOrigin().z );
+ m_vecDesired = pRecharger->GetAbsOrigin() - m_posDesired;
+
+ VectorNormalize( m_vecDesired );
+
+ m_vecDesired.z = 0;
+
+ VectorNormalize( m_vecDesired );
+ }
+ else
+ {
+ m_hRecharger = NULL;
+ Msg( "nihilanth can't find %s\n", szName );
+
+ m_iLevel++;
+
+ if ( m_iLevel > 9 )
+ m_irritation = 2;
+ }
+ }
+
+ float flDist = ( m_posDesired - GetAbsOrigin() ).Length();
+ float flDot = DotProduct( m_vecDesired, vForward );
+
+ if (m_hRecharger != NULL)
+ {
+ // at we at power up yet?
+ if (flDist < 128.0)
+ {
+ int iseq = LookupSequence( "recharge" );
+
+ if (iseq != GetSequence())
+ {
+ char szText[64];
+
+ Q_snprintf( szText, sizeof( szText ), "%s%d", m_szDrawUse, m_iLevel );
+ FireTargets( szText, this, this, USE_ON, 1.0 );
+
+ Msg( "fireing %s\n", szText );
+ }
+ SetSequence ( LookupSequence( "recharge" ) );
+ }
+ else
+ {
+ FloatSequence( );
+ }
+ return;
+ }
+
+ if (GetEnemy() != NULL && !GetEnemy()->IsAlive())
+ {
+ SetEnemy( NULL );
+ }
+
+ if (m_flLastSeen + 15 < gpGlobals->curtime)
+ {
+ SetEnemy( NULL );
+ }
+
+ if ( GetEnemy() == NULL)
+ {
+ GetSenses()->Look( 4096 );
+ SetEnemy( BestEnemy() );
+ }
+
+ if ( GetEnemy() != NULL && m_irritation != 0)
+ {
+ if (m_flLastSeen + 5 > gpGlobals->curtime && flDist < 256 && flDot > 0)
+ {
+ if (m_irritation >= 2 && m_iHealth < sk_nihilanth_health.GetFloat() / 2.0)
+ {
+ SetSequence( LookupSequence( "attack1_open" ) );
+ }
+ else
+ {
+ if ( random->RandomInt(0, 1 ) == 0)
+ {
+ SetSequence( LookupSequence( "attack1" ) ); // zap
+ }
+ else
+ {
+ char szText[64];
+
+ Q_snprintf( szText, sizeof( szText ), "%s%d", m_szTeleportTouch, m_iTeleport );
+ CBaseEntity *pTouch = gEntList.FindEntityByName( NULL, szText );
+
+ Q_snprintf( szText, sizeof( szText ), "%s%d", m_szTeleportUse, m_iTeleport );
+ CBaseEntity *pTrigger = gEntList.FindEntityByName( NULL, szText );
+
+ if (pTrigger != NULL || pTouch != NULL)
+ {
+ SetSequence( LookupSequence( "attack2" ) ); // teleport
+ }
+ else
+ {
+ m_iTeleport++;
+ SetSequence( LookupSequence( "attack1" ) ); // zap
+ }
+ }
+ }
+ return;
+ }
+ }
+
+ FloatSequence( );
+}
+
+void CNPC_Nihilanth::MakeFriend( Vector vecStart )
+{
+ int i;
+
+ for (i = 0; i < 3; i++)
+ {
+ if (m_hFriend[i] != NULL && !m_hFriend[i]->IsAlive())
+ {
+ if ( m_nRenderMode == kRenderNormal) // don't do it if they are already fading
+ m_hFriend[i]->MyNPCPointer()->CorpseFade();
+
+ m_hFriend[i] = NULL;
+ }
+
+ if (m_hFriend[i] == NULL)
+ {
+ if ( random->RandomInt( 0, 1 ) == 0)
+ {
+ int iNode = GetNavigator()->GetNetwork()->NearestNodeToPoint( vecStart );
+ CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( iNode );
+
+ if ( pNode && pNode->GetType() == NODE_AIR )
+ {
+ trace_t tr;
+ Vector vNodeOrigin = pNode->GetOrigin();
+
+ UTIL_TraceHull( vNodeOrigin + Vector( 0, 0, 32 ), vNodeOrigin + Vector( 0, 0, 32 ), Vector(-40,-40, 0), Vector(40, 40, 100), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.startsolid == 0 )
+ m_hFriend[i] = Create("monster_alien_controller", vNodeOrigin, GetAbsAngles() );
+ }
+ }
+ else
+ {
+ int iNode = GetNavigator()->GetNetwork()->NearestNodeToPoint( vecStart );
+ CAI_Node *pNode = GetNavigator()->GetNetwork()->GetNode( iNode );
+
+ if ( pNode && ( pNode->GetType() == NODE_GROUND || pNode->GetType() == NODE_WATER ) )
+ {
+ trace_t tr;
+ Vector vNodeOrigin = pNode->GetOrigin();
+
+ UTIL_TraceHull( vNodeOrigin + Vector( 0, 0, 36 ), vNodeOrigin + Vector( 0, 0, 36 ), Vector( -15, -15, 0), Vector( 20, 15, 72 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ if (tr.startsolid == 0)
+ m_hFriend[i] = Create("monster_alien_slave", vNodeOrigin, GetAbsAngles() );
+ }
+ }
+ if (m_hFriend[i] != NULL)
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, m_hFriend[i]->entindex(), "Nihilanth.FriendBeam" );
+ }
+
+ return;
+ }
+ }
+}
+
+void CNPC_Nihilanth::ShootBalls( void )
+{
+ if (m_flShootEnd > gpGlobals->curtime)
+ {
+ Vector vecHand;
+ QAngle vecAngle;
+
+ while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->curtime)
+ {
+ if ( GetEnemy() != NULL)
+ {
+ Vector vecSrc, vecDir;
+ CNihilanthHVR *pEntity = NULL;
+
+ GetAttachment( 3, vecHand, vecAngle );
+ vecSrc = vecHand + GetAbsVelocity() * (m_flShootTime - gpGlobals->curtime);
+ vecDir = m_posTarget - GetAbsOrigin();
+ VectorNormalize( vecDir );
+ vecSrc = vecSrc + vecDir * (gpGlobals->curtime - m_flShootTime);
+
+ pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR, "nihilanth_energy_ball" );
+
+ pEntity->SetAbsOrigin( vecSrc );
+ pEntity->SetAbsAngles( vecAngle );
+ pEntity->SetOwnerEntity( this );
+ pEntity->Spawn();
+
+ pEntity->SetAbsVelocity( vecDir * 200.0 );
+ pEntity->ZapInit( GetEnemy() );
+
+ GetAttachment( 4, vecHand, vecAngle );
+ vecSrc = vecHand + GetAbsVelocity() * (m_flShootTime - gpGlobals->curtime);
+ vecDir = m_posTarget - GetAbsOrigin();
+ VectorNormalize( vecDir );
+
+ vecSrc = vecSrc + vecDir * (gpGlobals->curtime - m_flShootTime);
+
+ pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR, "nihilanth_energy_ball" );
+
+ pEntity->SetAbsOrigin( vecSrc );
+ pEntity->SetAbsAngles( vecAngle );
+ pEntity->SetOwnerEntity( this );
+ pEntity->Spawn();
+
+ pEntity->SetAbsVelocity( vecDir * 200.0 );
+ pEntity->ZapInit( GetEnemy() );
+
+ }
+
+ m_flShootTime += 0.2;
+ }
+ }
+}
+
+void CNPC_Nihilanth::FloatSequence( void )
+{
+ if (m_irritation >= 2)
+ {
+ SetSequence( LookupSequence( "float_open" ) );
+ }
+ else if (m_avelocity.y > 30)
+ {
+ SetSequence( LookupSequence( "walk_r" ) );
+ }
+ else if (m_avelocity.y < -30)
+ {
+ SetSequence( LookupSequence( "walk_l" ) );
+ }
+ else if (m_velocity.z > 30)
+ {
+ SetSequence( LookupSequence( "walk_u" ) );
+ }
+ else if (m_velocity.z < -30)
+ {
+ SetSequence( LookupSequence( "walk_d" ) );
+ }
+ else
+ {
+ SetSequence( LookupSequence( "float" ) );
+ }
+}
+
+void CNPC_Nihilanth::DyingThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ DispatchAnimEvents( this );
+ StudioFrameAdvance( );
+
+ if ( m_lifeState == LIFE_ALIVE )
+ {
+ CTakeDamageInfo info;
+ DeathSound( info );
+ m_lifeState = LIFE_DYING;
+
+ m_posDesired.z = m_flMaxZ;
+ }
+
+ if ( GetAbsOrigin().z < m_flMaxZ && m_lifeState == LIFE_DEAD )
+ {
+ SetAbsOrigin( Vector( GetAbsOrigin().x, GetAbsOrigin().y, m_flMaxZ ) );
+ SetAbsVelocity( Vector( 0, 0, 0 ) );
+ }
+
+ if ( m_lifeState == LIFE_DYING )
+ {
+ Flight( );
+
+ if (fabs( GetAbsOrigin().z - m_flMaxZ ) < 16)
+ {
+ CBaseEntity *pTrigger = NULL;
+
+ SetAbsVelocity( Vector( 0, 0, 0 ) );
+ SetGravity( 0 );
+
+ while( ( pTrigger = gEntList.FindEntityByName( pTrigger, m_szDeadUse ) ) != NULL )
+ {
+ CLogicRelay *pRelay = (CLogicRelay*)pTrigger;
+ pRelay->m_OnTrigger.FireOutput( this, this );
+ }
+
+ m_lifeState = LIFE_DEAD;
+ }
+ }
+
+ if ( IsSequenceFinished() )
+ {
+ QAngle qAngularVel = GetLocalAngularVelocity();
+
+ qAngularVel.y += random->RandomFloat( -100, 100 );
+
+ if ( qAngularVel.y < -100)
+ qAngularVel.y = -100;
+ if ( qAngularVel.y > 100)
+ qAngularVel.y = 100;
+
+ SetLocalAngularVelocity( qAngularVel );
+ SetSequence( LookupSequence( "die1" ) );
+ }
+
+ if ( m_pBall )
+ {
+ if (m_pBall->GetBrightness() > 0)
+ {
+ m_pBall->SetBrightness( MAX( 0, m_pBall->GetBrightness() - 7 ), 0 );
+ }
+ else
+ {
+ UTIL_Remove( m_pBall );
+ m_pBall = NULL;
+ }
+ }
+
+ Vector vecDir, vecSrc;
+ QAngle vecAngles;
+ Vector vForward, vRight, vUp;
+
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ int iAttachment = random->RandomInt( 1, 4 );
+
+ do {
+ vecDir = Vector( random->RandomFloat( -1, 1 ), random->RandomFloat( -1, 1 ), random->RandomFloat( -1, 1 ) );
+ } while (DotProduct( vecDir, vecDir) > 1.0);
+
+ switch( random->RandomInt( 1, 4 ))
+ {
+ case 1: // head
+ vecDir.z = fabs( vecDir.z ) * 0.5;
+ vecDir = vecDir + 2 * vUp;
+ break;
+ case 2: // eyes
+ if (DotProduct( vecDir, vForward ) < 0)
+ vecDir = vecDir * -1;
+
+ vecDir = vecDir + 2 * vForward;
+ break;
+ case 3: // left hand
+ if (DotProduct( vecDir, vRight ) > 0)
+ vecDir = vecDir * -1;
+ vecDir = vecDir - 2 * vRight;
+ break;
+ case 4: // right hand
+ if (DotProduct( vecDir, vRight ) < 0)
+ vecDir = vecDir * -1;
+ vecDir = vecDir + 2 * vRight;
+ break;
+ }
+
+ GetAttachment( iAttachment, vecSrc, vecAngles );
+
+ trace_t tr;
+
+ UTIL_TraceLine( vecSrc, vecSrc + vecDir * 4096, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ CBeam *pBeam = CBeam::BeamCreate( "sprites/laserbeam.vmt", 16 );
+
+ if ( pBeam == NULL )
+ return;
+
+ pBeam->PointEntInit( tr.endpos, this );
+ pBeam->SetEndAttachment( iAttachment );
+ pBeam->SetColor( 64, 128, 255 );
+ pBeam->SetFadeLength( 50 );
+ pBeam->SetBrightness( 255 );
+ pBeam->SetNoise( 12 );
+ pBeam->SetScrollRate( 1.0 );
+ pBeam->LiveForTime( 0.5 );
+
+ GetAttachment( 2, vecSrc, vecAngles );
+ CNihilanthHVR *pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR, "nihilanth_energy_ball" );
+
+ pEntity->SetAbsOrigin( vecSrc );
+ pEntity->SetAbsAngles( GetAbsAngles() );
+ pEntity->SetOwnerEntity( this );
+ pEntity->SetAbsVelocity( Vector ( random->RandomFloat( -0.7, 0.7 ), random->RandomFloat( -0.7, 0.7 ), 1.0 ) * 600.0 );
+ pEntity->Spawn();
+
+ pEntity->GreenBallInit();
+
+ return;
+}
+
+
+void CNPC_Nihilanth::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case 1: // shoot
+ break;
+ case 2: // zen
+ if ( GetEnemy() != NULL)
+ {
+ Vector vOrigin;
+ QAngle vAngle;
+ CPASAttenuationFilter filter( this );
+
+ if ( random->RandomInt(0,4) == 0)
+ EmitSound( filter, entindex(), "Nihilanth.Attack" );
+
+ EmitSound( filter, entindex(), "Nihilanth.BallAttack" );
+
+ GetAttachment( 2, vOrigin, vAngle);
+
+ CBroadcastRecipientFilter filterlight;
+
+ te->DynamicLight( filterlight, 0.0, &vOrigin, 128, 128, 255, 0, 256, 1.0f, 128 );
+
+ GetAttachment( 3, vOrigin, vAngle);
+ te->DynamicLight( filterlight, 0.0, &vOrigin, 128, 128, 255, 0, 256, 1.0f, 128 );
+
+ m_flShootTime = gpGlobals->curtime;
+ m_flShootEnd = gpGlobals->curtime + 1.0;
+ }
+ break;
+ case 3: // prayer
+ if (GetEnemy() != NULL)
+ {
+ char szText[32];
+
+ Q_snprintf( szText, sizeof( szText ), "%s%d", m_szTeleportTouch, m_iTeleport );
+ CBaseEntity *pTouch = gEntList.FindEntityByName( NULL, szText );
+
+ Q_snprintf( szText, sizeof( szText ), "%s%d", m_szTeleportUse, m_iTeleport );
+ CBaseEntity *pTrigger = gEntList.FindEntityByName( NULL, szText );
+
+ if (pTrigger != NULL || pTouch != NULL)
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Nihilanth.Attack" );
+
+ Vector vecSrc;
+ QAngle vecAngles;
+
+ GetAttachment( 2, vecSrc, vecAngles );
+
+ CNihilanthHVR *pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR, "nihilanth_energy_ball" );
+
+ pEntity->SetAbsOrigin( vecSrc );
+ pEntity->SetAbsAngles( vecAngles );
+ pEntity->SetOwnerEntity( this );
+ pEntity->Spawn();
+
+ pEntity->TeleportInit( this, GetEnemy(), pTrigger, pTouch );
+
+ pEntity->SetAbsVelocity( GetAbsOrigin() - vecSrc );
+ pEntity->SetAbsVelocity( Vector( GetAbsVelocity().x, GetAbsVelocity().y, GetAbsVelocity().z * 0.2 ) );
+
+ }
+ else
+ {
+ Vector vOrigin;
+ QAngle vAngle;
+
+ m_iTeleport++; // unexpected failure
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Nihilanth.BallAttack" );
+
+ Msg( "nihilanth can't target %s\n", szText );
+
+ GetAttachment( 2, vOrigin, vAngle);
+
+ CBroadcastRecipientFilter filterlight;
+
+ te->DynamicLight( filterlight, 0.0, &vOrigin, 128, 128, 255, 0, 256, 1.0f, 128 );
+
+ GetAttachment( 3, vOrigin, vAngle);
+ te->DynamicLight( filterlight, 0.0, &vOrigin, 128, 128, 255, 0, 256, 1.0f, 128 );
+
+ m_flShootTime = gpGlobals->curtime;
+ m_flShootEnd = gpGlobals->curtime + 1.0;
+ }
+ }
+ break;
+ case 4: // get a sphere
+ {
+ if (m_hRecharger != NULL)
+ {
+ if (!EmitSphere( ))
+ {
+ m_hRecharger = NULL;
+ }
+ }
+ }
+ break;
+ case 5: // start up sphere machine
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Nihilanth.Recharge" );
+ }
+ break;
+ case 6:
+ if ( GetEnemy() != NULL)
+ {
+ Vector vecSrc;
+ QAngle vecAngles;
+ GetAttachment( 3, vecSrc, vecAngles );
+
+ CNihilanthHVR *pEntity = (CNihilanthHVR *)CREATE_ENTITY( CNihilanthHVR, "nihilanth_energy_ball" );
+
+ pEntity->SetAbsOrigin( vecSrc );
+ pEntity->SetAbsAngles( vecAngles );
+ pEntity->SetOwnerEntity( this );
+ pEntity->Spawn();
+
+ pEntity->SetAbsVelocity( GetAbsOrigin() - vecSrc );
+ pEntity->ZapInit( GetEnemy() );
+ }
+ break;
+ case 7:
+ /*
+ Vector vecSrc, vecAngles;
+ GetAttachment( 0, vecSrc, vecAngles );
+ CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() );
+ pEntity->pev->velocity = Vector ( RANDOM_FLOAT( -0.7, 0.7 ), RANDOM_FLOAT( -0.7, 0.7 ), 1.0 ) * 600.0;
+ pEntity->GreenBallInit( );
+ */
+ break;
+ }
+}
+
+
+//=========================================================
+// Controller bouncy ball attack
+//=========================================================
+
+
+
+void CNihilanthHVR::Spawn( void )
+{
+ Precache( );
+}
+
+
+void CNihilanthHVR::Precache( void )
+{
+ PrecacheModel("sprites/flare6.vmt");
+ PrecacheModel("sprites/nhth1.vmt");
+ PrecacheModel("sprites/exit1.vmt");
+ PrecacheModel("sprites/tele1.vmt");
+ PrecacheModel("sprites/animglow01.vmt");
+ PrecacheModel("sprites/xspark4.vmt");
+ PrecacheModel("sprites/muzzleflash3.vmt");
+
+ PrecacheModel("sprites/laserbeam.vmt");
+
+ PrecacheScriptSound( "NihilanthHVR.Zap" );
+ PrecacheScriptSound( "NihilanthHVR.TeleAttack" );
+}
+
+void CNihilanthHVR::CircleInit( CBaseEntity *pTarget )
+{
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_NONE );
+
+ UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0));
+ UTIL_SetOrigin( this, GetAbsOrigin() );
+
+ SetThink( &CNihilanthHVR::HoverThink );
+ SetTouch( &CNihilanthHVR::BounceTouch );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ CSprite *pSprite = SpriteInit( "sprites/muzzleflash3.vmt", this );
+
+ if ( pSprite )
+ {
+ m_flBallScale = 2.0f;
+ pSprite->SetScale( 2.0 );
+ pSprite->SetTransparency( kRenderTransAdd, 255, 224, 192, 255, kRenderFxNone );
+ }
+
+ SetTarget( pTarget );
+}
+
+CSprite *CNihilanthHVR::SpriteInit( const char *pSpriteName, CNihilanthHVR *pOwner )
+{
+ pOwner->SetSprite( CSprite::SpriteCreate( pSpriteName, pOwner->GetAbsOrigin(), true ) );
+
+ CSprite *pSprite = (CSprite*)pOwner->GetSprite();
+
+ if ( pSprite )
+ {
+ pSprite->SetAttachment( pOwner, 0 );
+ pSprite->SetOwnerEntity( pOwner );
+ pSprite->AnimateForTime( 5, 9999 );
+ }
+
+ return pSprite;
+}
+
+void CNihilanthHVR::HoverThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ if ( GetTarget() != NULL )
+ {
+ CircleTarget( GetTarget()->GetAbsOrigin() + Vector( 0, 0, 16 * N_SCALE ) );
+ }
+ else
+ {
+ UTIL_Remove( GetSprite() );
+ UTIL_Remove( this );
+ }
+}
+
+void CNihilanthHVR::BounceTouch( CBaseEntity *pOther )
+{
+ Vector vecDir = m_vecIdeal;
+
+ VectorNormalize( vecDir );
+
+ const trace_t &tr = GetTouchTrace();
+
+ float n = -DotProduct(tr.plane.normal, vecDir);
+
+ vecDir = 2.0 * tr.plane.normal * n + vecDir;
+
+ m_vecIdeal = vecDir * m_vecIdeal.Length();
+}
+
+bool CNihilanthHVR::CircleTarget( Vector vecTarget )
+{
+ bool fClose = false;
+
+ vecTarget = vecTarget + Vector( 0, 0, 64 );
+
+ Vector vecDest = vecTarget;
+ Vector vecEst = GetAbsOrigin() + GetAbsVelocity() * 0.5;
+ Vector vecSrc = GetAbsOrigin();
+ vecDest.z = 0;
+ vecEst.z = 0;
+ vecSrc.z = 0;
+ float d1 = (vecDest - vecSrc).Length() - 24 * N_SCALE;
+ float d2 = (vecDest - vecEst).Length() - 24 * N_SCALE;
+
+ if ( m_vecIdeal == vec3_origin )
+ {
+ m_vecIdeal = GetAbsVelocity();
+ }
+
+ if (d1 < 0 && d2 <= d1)
+ {
+ // ALERT( at_console, "too close\n");
+ Vector vTemp = (vecDest - vecSrc);
+
+ VectorNormalize( vTemp );
+
+ m_vecIdeal = m_vecIdeal - vTemp * 50;
+ }
+
+ else if (d1 > 0 && d2 >= d1)
+ {
+ Vector vTemp = (vecDest - vecSrc);
+
+ VectorNormalize( vTemp );
+
+ m_vecIdeal = m_vecIdeal + vTemp * 50;
+ }
+
+ SetLocalAngularVelocity( QAngle( GetLocalAngularVelocity().x, GetLocalAngularVelocity().y, d1 * 20 ) );
+
+ if (d1 < 32)
+ {
+ fClose = true;
+ }
+
+ m_vecIdeal = m_vecIdeal + Vector( random->RandomFloat( -2, 2 ), random->RandomFloat( -2, 2 ), random->RandomFloat( -2, 2 ));
+
+ float flIdealZ = m_vecIdeal.z;
+
+ m_vecIdeal = Vector( m_vecIdeal.x, m_vecIdeal.y, 0 );
+
+ VectorNormalize( m_vecIdeal );
+
+ m_vecIdeal = (m_vecIdeal * 200) + Vector( 0, 0, flIdealZ );
+
+ // move up/down
+ d1 = vecTarget.z - GetAbsOrigin().z;
+ if (d1 > 0 && m_vecIdeal.z < 200)
+ m_vecIdeal.z += 200;
+ else if (d1 < 0 && m_vecIdeal.z > -200)
+ m_vecIdeal.z -= 200;
+
+ SetAbsVelocity( m_vecIdeal );
+
+ // ALERT( at_console, "%.0f %.0f %.0f\n", m_vecIdeal.x, m_vecIdeal.y, m_vecIdeal.z );
+ return fClose;
+}
+
+
+void CNihilanthHVR::ZapInit( CBaseEntity *pEnemy )
+{
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+
+ CSprite *pSprite = SpriteInit( "sprites/nhth1.vmt", this );
+
+ if ( pSprite )
+ {
+ m_flBallScale = 2.0f;
+ pSprite->SetScale( 2.0 );
+ pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
+ }
+
+
+ Vector vVelocity = pEnemy->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize( vVelocity );
+ SetAbsVelocity ( vVelocity * 300 );
+
+ SetEnemy( pEnemy );
+ SetThink( &CNihilanthHVR::ZapThink );
+ SetTouch( &CNihilanthHVR::ZapTouch );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NihilanthHVR.Zap" );
+}
+
+void CNihilanthHVR::ZapThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.05 );
+
+ // check world boundaries
+ if ( GetEnemy() == NULL || GetAbsOrigin().x < -4096 || GetAbsOrigin().x > 4096 || GetAbsOrigin().y < -4096 || GetAbsOrigin().y > 4096 || GetAbsOrigin().z < -4096 || GetAbsOrigin().z > 4096)
+ {
+ SetTouch( NULL );
+ UTIL_Remove( GetSprite() );
+ UTIL_Remove( this );
+ return;
+ }
+
+ if ( GetAbsVelocity().Length() < 2000)
+ {
+ SetAbsVelocity( GetAbsVelocity() * 1.2 );
+ }
+
+ if (( GetEnemy()->WorldSpaceCenter() - GetAbsOrigin()).Length() < 256)
+ {
+ trace_t tr;
+
+ UTIL_TraceLine( GetAbsOrigin(), GetEnemy()->WorldSpaceCenter(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ CBaseEntity *pEntity = tr.m_pEnt;
+
+ if (pEntity != NULL && pEntity->m_takedamage )
+ {
+ ClearMultiDamage( );
+ CTakeDamageInfo info( this, this, sk_nihilanth_zap.GetFloat(), DMG_SHOCK );
+ CalculateMeleeDamageForce( &info, (tr.endpos - tr.startpos), tr.endpos );
+ pEntity->DispatchTraceAttack( info, GetAbsVelocity(), &tr );
+ ApplyMultiDamage();
+ }
+
+ CBeam *pBeam = CBeam::BeamCreate( "sprites/laserbeam.vmt", 2.0f );
+
+ if ( pBeam == NULL )
+ return;
+
+ pBeam->PointEntInit( tr.endpos, this );
+ pBeam->SetColor( 64, 196, 255 );
+ pBeam->SetBrightness( 255 );
+ pBeam->SetNoise( 7.2 );
+ pBeam->SetScrollRate( 10 );
+ pBeam->LiveForTime( 0.1 );
+
+ UTIL_EmitAmbientSound( GetSoundSourceIndex(), tr.endpos, "Controller.ElectroSound", 0.5, SNDLVL_NORM, 0, random->RandomInt( 140, 160 ) );
+
+ SetTouch( NULL );
+ GetSprite()->SetThink( &CBaseEntity::SUB_Remove );
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.2 );
+ GetSprite()->SetNextThink( gpGlobals->curtime + 0.2 );
+ return;
+ }
+
+ CBroadcastRecipientFilter filterlight;
+ te->DynamicLight( filterlight, 0.0, &GetAbsOrigin(), 128, 128, 255, 0, 128, 10, 128 );
+}
+
+
+void CNihilanthHVR::ZapTouch( CBaseEntity *pOther )
+{
+ UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin(), "Controller.ElectroSound", 1.0, SNDLVL_NORM, 0, random->RandomInt( 90, 95 ) );
+
+ RadiusDamage( CTakeDamageInfo( this, this, 50, DMG_SHOCK ), GetAbsOrigin(), 125, CLASS_NONE, NULL );
+ SetAbsVelocity( GetAbsVelocity() * 0 );
+
+ SetTouch( NULL );
+ UTIL_Remove( GetSprite() );
+ UTIL_Remove( this );
+ SetNextThink( gpGlobals->curtime + 0.2 );
+}
+
+void CNihilanthHVR::AbsorbInit( void )
+{
+ CBroadcastRecipientFilter filter;
+
+ SetThink( &CNihilanthHVR::DissipateThink );
+ SetRenderColorA( 255 );
+
+ SetBeam( CBeam::BeamCreate( "sprites/laserbeam.vmt", 8.0f ) );
+
+ CBeam *pBeam = (CBeam*)GetBeam();
+
+ if ( pBeam == NULL )
+ return;
+
+ pBeam->EntsInit( this, GetTarget() );
+ pBeam->SetEndAttachment( 1 );
+ pBeam->SetColor( 255, 128, 64 );
+ pBeam->SetBrightness( 255 );
+ pBeam->SetNoise( 18 );
+}
+
+void CNihilanthHVR::DissipateThink( void )
+{
+ CSprite *pSprite = (CSprite*)GetSprite();
+
+ SetNextThink ( gpGlobals->curtime + 0.1 );
+
+ if ( m_flBallScale > 5.0)
+ {
+ UTIL_Remove( this );
+ UTIL_Remove( GetSprite() );
+ UTIL_Remove( GetBeam() );
+ }
+
+ pSprite->SetBrightness( pSprite->GetBrightness() - 7, 0 );
+
+ m_flBallScale += 0.1;
+ pSprite->SetScale( m_flBallScale );
+
+ if ( GetTarget() != NULL)
+ {
+ CircleTarget( GetTarget()->GetAbsOrigin() + Vector( 0, 0, 4096 ) );
+ }
+ else
+ {
+ UTIL_Remove( this );
+ UTIL_Remove( GetSprite() );
+ UTIL_Remove( GetBeam() );
+ }
+
+/* MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
+ WRITE_BYTE( TE_ELIGHT );
+ WRITE_SHORT( entindex( ) ); // entity, attachment
+ WRITE_COORD( pev->origin.x ); // origin
+ WRITE_COORD( pev->origin.y );
+ WRITE_COORD( pev->origin.z );
+ WRITE_COORD( pev->renderamt ); // radius
+ WRITE_BYTE( 255 ); // R
+ WRITE_BYTE( 192 ); // G
+ WRITE_BYTE( 64 ); // B
+ WRITE_BYTE( 2 ); // life * 10
+ WRITE_COORD( 0 ); // decay
+ MESSAGE_END();*/
+}
+
+void CNihilanthHVR::TeleportInit( CNPC_Nihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch )
+{
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+
+ SetModel( "" );
+
+ CSprite *pSprite = SpriteInit( "sprites/exit1.vmt", this );
+
+ if ( pSprite )
+ {
+ m_flBallScale = 2.0f;
+ pSprite->SetScale( 2.0 );
+ pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
+ }
+
+
+ m_pNihilanth = pOwner;
+ SetEnemy( pEnemy );
+ SetTarget( pTarget );
+ m_hTouch = pTouch;
+
+ SetThink( &CNihilanthHVR::TeleportThink );
+ SetTouch( &CNihilanthHVR::TeleportTouch );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "NihilanthHVR.TeleAttack" );
+}
+
+void CNihilanthHVR::MovetoTarget( Vector vecTarget )
+{
+ if ( m_vecIdeal == vec3_origin )
+ {
+ m_vecIdeal = GetAbsVelocity();
+ }
+
+ // accelerate
+ float flSpeed = m_vecIdeal.Length();
+
+ if (flSpeed > 300)
+ {
+ VectorNormalize( m_vecIdeal );
+ m_vecIdeal= m_vecIdeal * 300;
+ }
+
+ Vector vTemp = vecTarget - GetAbsOrigin();
+ VectorNormalize( vTemp );
+
+ m_vecIdeal = m_vecIdeal + vTemp * 300;
+ SetAbsVelocity( m_vecIdeal );
+}
+
+void CNihilanthHVR::TeleportThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ // check world boundaries
+ if ( GetEnemy() == NULL || !GetEnemy()->IsAlive() || GetAbsOrigin().x < -4096 || GetAbsOrigin().x > 4096 || GetAbsOrigin().y < -4096 || GetAbsOrigin().y > 4096 || GetAbsOrigin().z < -4096 || GetAbsOrigin().z > 4096)
+ {
+ StopSound( entindex(), "NihilanthHVR.TeleAttack" );
+ UTIL_Remove( this );
+ UTIL_Remove( GetSprite() );
+ return;
+ }
+
+ if (( GetEnemy()->WorldSpaceCenter() - GetAbsOrigin() ).Length() < 128)
+ {
+ StopSound( entindex(), "NihilanthHVR.TeleAttack" );
+ UTIL_Remove( this );
+ UTIL_Remove( GetSprite() );
+
+ if ( GetTarget() != NULL)
+ {
+ CLogicRelay *pRelay = (CLogicRelay*)GetTarget();
+ pRelay->m_OnTrigger.FireOutput( this, this );
+ }
+
+ if ( m_hTouch != NULL && GetEnemy() != NULL )
+ m_hTouch->Touch( GetEnemy() );
+ }
+ else
+ {
+ MovetoTarget( GetEnemy()->WorldSpaceCenter( ) );
+ }
+
+ CBroadcastRecipientFilter filterlight;
+ te->DynamicLight( filterlight, 0.0, &GetAbsOrigin(), 0, 255, 0, 0, 256, 1.0, 256 );
+}
+
+void CNihilanthHVR::TeleportTouch( CBaseEntity *pOther )
+{
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if (pOther == pEnemy)
+ {
+ if (GetTarget() != NULL)
+ {
+ if ( GetTarget() != NULL)
+ {
+ CLogicRelay *pRelay = (CLogicRelay*)GetTarget();
+ pRelay->m_OnTrigger.FireOutput( this, this );
+ }
+ }
+
+ if (m_hTouch != NULL && pEnemy != NULL )
+ m_hTouch->Touch( pEnemy );
+ }
+ else
+ {
+ m_pNihilanth->MakeFriend( GetAbsOrigin() );
+ }
+
+ SetTouch( NULL );
+ StopSound( entindex(), "NihilanthHVR.TeleAttack" );
+ UTIL_Remove( this );
+ UTIL_Remove( GetSprite() );
+}
+
+void CNihilanthHVR::GreenBallInit( )
+{
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+
+ SetModel( "" );
+
+ CSprite *pSprite = SpriteInit( "sprites/exit1.spr", this );
+
+ if ( pSprite )
+ {
+ pSprite->SetScale( 1.0 );
+ pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone );
+ }
+
+ SetTouch( &CNihilanthHVR::RemoveTouch );
+}
+
+void CNihilanthHVR::RemoveTouch( CBaseEntity *pOther )
+{
+ StopSound( entindex(), "NihilanthHVR.TeleAttack" );
+ UTIL_Remove( this );
+ UTIL_Remove( GetSprite() );
+}