summaryrefslogtreecommitdiff
path: root/game/server/hl1
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
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/hl1')
-rw-r--r--game/server/hl1/hl1_ai_basenpc.cpp215
-rw-r--r--game/server/hl1/hl1_ai_basenpc.h51
-rw-r--r--game/server/hl1/hl1_basecombatweapon.cpp86
-rw-r--r--game/server/hl1/hl1_basegrenade.cpp116
-rw-r--r--game/server/hl1/hl1_basegrenade.h42
-rw-r--r--game/server/hl1/hl1_client.cpp199
-rw-r--r--game/server/hl1/hl1_ents.cpp1654
-rw-r--r--game/server/hl1/hl1_ents.h73
-rw-r--r--game/server/hl1/hl1_env_speaker.cpp219
-rw-r--r--game/server/hl1/hl1_eventlog.cpp55
-rw-r--r--game/server/hl1/hl1_func_recharge.cpp239
-rw-r--r--game/server/hl1/hl1_func_tank.cpp1700
-rw-r--r--game/server/hl1/hl1_grenade_mp5.cpp160
-rw-r--r--game/server/hl1/hl1_grenade_mp5.h42
-rw-r--r--game/server/hl1/hl1_grenade_spit.cpp171
-rw-r--r--game/server/hl1/hl1_grenade_spit.h49
-rw-r--r--game/server/hl1/hl1_item_ammo.cpp410
-rw-r--r--game/server/hl1/hl1_item_battery.cpp78
-rw-r--r--game/server/hl1/hl1_item_healthkit.cpp384
-rw-r--r--game/server/hl1/hl1_item_longjump.cpp61
-rw-r--r--game/server/hl1/hl1_item_suit.cpp61
-rw-r--r--game/server/hl1/hl1_items.cpp45
-rw-r--r--game/server/hl1/hl1_items.h26
-rw-r--r--game/server/hl1/hl1_monstermaker.cpp294
-rw-r--r--game/server/hl1/hl1_monstermaker.h71
-rw-r--r--game/server/hl1/hl1_npc_aflock.cpp858
-rw-r--r--game/server/hl1/hl1_npc_agrunt.cpp1127
-rw-r--r--game/server/hl1/hl1_npc_apache.cpp766
-rw-r--r--game/server/hl1/hl1_npc_barnacle.cpp516
-rw-r--r--game/server/hl1/hl1_npc_barnacle.h63
-rw-r--r--game/server/hl1/hl1_npc_barney.cpp938
-rw-r--r--game/server/hl1/hl1_npc_barney.h81
-rw-r--r--game/server/hl1/hl1_npc_bigmomma.cpp1313
-rw-r--r--game/server/hl1/hl1_npc_bloater.cpp86
-rw-r--r--game/server/hl1/hl1_npc_bullsquid.cpp1167
-rw-r--r--game/server/hl1/hl1_npc_bullsquid.h68
-rw-r--r--game/server/hl1/hl1_npc_controller.cpp1313
-rw-r--r--game/server/hl1/hl1_npc_controller.h170
-rw-r--r--game/server/hl1/hl1_npc_gargantua.cpp1174
-rw-r--r--game/server/hl1/hl1_npc_gargantua.h86
-rw-r--r--game/server/hl1/hl1_npc_gman.cpp232
-rw-r--r--game/server/hl1/hl1_npc_hassassin.cpp951
-rw-r--r--game/server/hl1/hl1_npc_headcrab.cpp698
-rw-r--r--game/server/hl1/hl1_npc_headcrab.h63
-rw-r--r--game/server/hl1/hl1_npc_hgrunt.cpp2645
-rw-r--r--game/server/hl1/hl1_npc_hgrunt.h117
-rw-r--r--game/server/hl1/hl1_npc_hornet.cpp400
-rw-r--r--game/server/hl1/hl1_npc_hornet.h76
-rw-r--r--game/server/hl1/hl1_npc_houndeye.cpp1287
-rw-r--r--game/server/hl1/hl1_npc_houndeye.h73
-rw-r--r--game/server/hl1/hl1_npc_ichthyosaur.cpp1024
-rw-r--r--game/server/hl1/hl1_npc_ichthyosaur.h97
-rw-r--r--game/server/hl1/hl1_npc_leech.cpp724
-rw-r--r--game/server/hl1/hl1_npc_nihilanth.cpp1745
-rw-r--r--game/server/hl1/hl1_npc_osprey.cpp1589
-rw-r--r--game/server/hl1/hl1_npc_roach.cpp471
-rw-r--r--game/server/hl1/hl1_npc_scientist.cpp1410
-rw-r--r--game/server/hl1/hl1_npc_scientist.h139
-rw-r--r--game/server/hl1/hl1_npc_snark.cpp527
-rw-r--r--game/server/hl1/hl1_npc_snark.h57
-rw-r--r--game/server/hl1/hl1_npc_talker.cpp728
-rw-r--r--game/server/hl1/hl1_npc_talker.h125
-rw-r--r--game/server/hl1/hl1_npc_tentacle.cpp1012
-rw-r--r--game/server/hl1/hl1_npc_turret.cpp1509
-rw-r--r--game/server/hl1/hl1_npc_vortigaunt.cpp743
-rw-r--r--game/server/hl1/hl1_npc_vortigaunt.h74
-rw-r--r--game/server/hl1/hl1_npc_zombie.cpp307
-rw-r--r--game/server/hl1/hl1_npc_zombie.h52
-rw-r--r--game/server/hl1/hl1_player.cpp2110
-rw-r--r--game/server/hl1/hl1_player.h167
-rw-r--r--game/server/hl1/hl1_playermove.cpp76
-rw-r--r--game/server/hl1/hl1_weapon_crowbar.cpp379
-rw-r--r--game/server/hl1/hl1_weapon_snark.cpp208
-rw-r--r--game/server/hl1/hl1_weapon_tripmine.cpp573
-rw-r--r--game/server/hl1/hl1_weaponbox.cpp242
-rw-r--r--game/server/hl1/hl1mp_bot_temp.cpp432
-rw-r--r--game/server/hl1/hl1mp_bot_temp.h19
-rw-r--r--game/server/hl1/hl1mp_gameinterface.cpp28
-rw-r--r--game/server/hl1/hl1mp_player.cpp636
-rw-r--r--game/server/hl1/hl1mp_player.h87
80 files changed, 40059 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_ai_basenpc.cpp b/game/server/hl1/hl1_ai_basenpc.cpp
new file mode 100644
index 0000000..f97faf0
--- /dev/null
+++ b/game/server/hl1/hl1_ai_basenpc.cpp
@@ -0,0 +1,215 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "hl1_ai_basenpc.h"
+#include "scripted.h"
+#include "soundent.h"
+#include "animation.h"
+#include "entitylist.h"
+#include "ai_navigator.h"
+#include "ai_motor.h"
+#include "player.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "npcevent.h"
+
+#include "effect_dispatch_data.h"
+#include "te_effect_dispatch.h"
+#include "cplane.h"
+#include "ai_squad.h"
+
+#define HUMAN_GIBS 1
+#define ALIEN_GIBS 2
+
+//=========================================================
+// NoFriendlyFire - checks for possibility of friendly fire
+//
+// Builds a large box in front of the grunt and checks to see
+// if any squad members are in that box.
+//=========================================================
+bool CHL1BaseNPC::NoFriendlyFire( void )
+{
+ if ( !m_pSquad )
+ {
+ return true;
+ }
+
+ CPlane backPlane;
+ CPlane leftPlane;
+ CPlane rightPlane;
+
+ Vector vecLeftSide;
+ Vector vecRightSide;
+ Vector v_left;
+
+ Vector vForward, vRight, vUp;
+ QAngle vAngleToEnemy;
+
+ if ( GetEnemy() != NULL )
+ {
+ //!!!BUGBUG - to fix this, the planes must be aligned to where the monster will be firing its gun, not the direction it is facing!!!
+ VectorAngles( ( GetEnemy()->WorldSpaceCenter() - GetAbsOrigin() ), vAngleToEnemy );
+
+ AngleVectors ( vAngleToEnemy, &vForward, &vRight, &vUp );
+ }
+ else
+ {
+ // if there's no enemy, pretend there's a friendly in the way, so the grunt won't shoot.
+ return false;
+ }
+
+ vecLeftSide = GetAbsOrigin() - ( vRight * ( WorldAlignSize().x * 1.5 ) );
+ vecRightSide = GetAbsOrigin() + ( vRight * ( WorldAlignSize().x * 1.5 ) );
+ v_left = vRight * -1;
+
+ leftPlane.InitializePlane ( vRight, vecLeftSide );
+ rightPlane.InitializePlane ( v_left, vecRightSide );
+ backPlane.InitializePlane ( vForward, GetAbsOrigin() );
+
+ AISquadIter_t iter;
+ for ( CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
+ {
+ if ( pSquadMember == NULL )
+ continue;
+
+ if ( pSquadMember == this )
+ continue;
+
+ if ( backPlane.PointInFront ( pSquadMember->GetAbsOrigin() ) &&
+ leftPlane.PointInFront ( pSquadMember->GetAbsOrigin() ) &&
+ rightPlane.PointInFront ( pSquadMember->GetAbsOrigin()) )
+ {
+ // this guy is in the check volume! Don't shoot!
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void CHL1BaseNPC::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ if ( info.GetDamage() >= 1.0 && !(info.GetDamageType() & DMG_SHOCK ) )
+ {
+ UTIL_BloodSpray( ptr->endpos, vecDir, BloodColor(), 4, FX_BLOODSPRAY_ALL );
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+
+bool CHL1BaseNPC::ShouldGib( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamageType() & DMG_NEVERGIB )
+ return false;
+
+ if ( ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) && m_iHealth < GIB_HEALTH_VALUE ) || ( info.GetDamageType() & DMG_ALWAYSGIB ) )
+ return true;
+
+ return false;
+
+}
+
+bool CHL1BaseNPC::HasHumanGibs( void )
+{
+ Class_T myClass = Classify();
+
+ if ( myClass == CLASS_HUMAN_MILITARY ||
+ myClass == CLASS_PLAYER_ALLY ||
+ myClass == CLASS_HUMAN_PASSIVE ||
+ myClass == CLASS_PLAYER )
+
+ return true;
+
+ return false;
+}
+
+
+bool CHL1BaseNPC::HasAlienGibs( void )
+{
+ Class_T myClass = Classify();
+
+ if ( myClass == CLASS_ALIEN_MILITARY ||
+ myClass == CLASS_ALIEN_MONSTER ||
+ myClass == CLASS_INSECT ||
+ myClass == CLASS_ALIEN_PREDATOR ||
+ myClass == CLASS_ALIEN_PREY )
+
+ return true;
+
+ return false;
+}
+
+
+void CHL1BaseNPC::Precache( void )
+{
+ PrecacheModel( "models/gibs/agibs.mdl" );
+ PrecacheModel( "models/gibs/hgibs.mdl" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CHL1BaseNPC::CorpseGib( const CTakeDamageInfo &info )
+{
+ CEffectData data;
+
+ data.m_vOrigin = WorldSpaceCenter();
+ data.m_vNormal = data.m_vOrigin - info.GetDamagePosition();
+ VectorNormalize( data.m_vNormal );
+
+ data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 );
+ data.m_flScale = clamp( data.m_flScale, 1, 3 );
+
+ if ( HasAlienGibs() )
+ data.m_nMaterial = ALIEN_GIBS;
+ else if ( HasHumanGibs() )
+ data.m_nMaterial = HUMAN_GIBS;
+
+ data.m_nColor = BloodColor();
+
+ DispatchEffect( "HL1Gib", data );
+
+ CSoundEnt::InsertSound( SOUND_MEAT, GetAbsOrigin(), 256, 0.5f, this );
+
+/// BaseClass::CorpseGib( info );
+
+ return true;
+}
+
+int CHL1BaseNPC::IRelationPriority( CBaseEntity *pTarget )
+{
+ return BaseClass::IRelationPriority( pTarget );
+}
+
+void CHL1BaseNPC::EjectShell( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int iType )
+{
+ CEffectData data;
+ data.m_vStart = vecVelocity;
+ data.m_vOrigin = vecOrigin;
+ data.m_vAngles = QAngle( 0, rotation, 0 );
+ data.m_fFlags = iType;
+
+ DispatchEffect( "HL1ShellEject", data );
+}
+
+// HL1 version - never return Ragdoll as the automatic schedule at the end of a
+// scripted sequence
+int CHL1BaseNPC::SelectDeadSchedule()
+{
+ // Alread dead (by animation event maybe?)
+ // Is it safe to set it to SCHED_NONE?
+ if ( m_lifeState == LIFE_DEAD )
+ return SCHED_NONE;
+
+ CleanupOnDeath();
+ return SCHED_DIE;
+}
diff --git a/game/server/hl1/hl1_ai_basenpc.h b/game/server/hl1/hl1_ai_basenpc.h
new file mode 100644
index 0000000..0b11ce1
--- /dev/null
+++ b/game/server/hl1/hl1_ai_basenpc.h
@@ -0,0 +1,51 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base combat character with no AI
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef HL1_AI_BASENPC_H
+#define HL1_AI_BASENPC_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "ai_basenpc.h"
+#include "ai_motor.h"
+//=============================================================================
+// >> CHL1NPCTalker
+//=============================================================================
+
+class CHL1BaseNPC : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CHL1BaseNPC, CAI_BaseNPC );
+
+public:
+ CHL1BaseNPC( void )
+ {
+
+ }
+
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ bool ShouldGib( const CTakeDamageInfo &info );
+ bool CorpseGib( const CTakeDamageInfo &info );
+
+ bool HasAlienGibs( void );
+ bool HasHumanGibs( void );
+
+ void Precache( void );
+
+ int IRelationPriority( CBaseEntity *pTarget );
+ bool NoFriendlyFire( void );
+
+ void EjectShell( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int iType );
+
+ virtual int SelectDeadSchedule();
+};
+
+#endif //HL1_AI_BASENPC_H
diff --git a/game/server/hl1/hl1_basecombatweapon.cpp b/game/server/hl1/hl1_basecombatweapon.cpp
new file mode 100644
index 0000000..39a4707
--- /dev/null
+++ b/game/server/hl1/hl1_basecombatweapon.cpp
@@ -0,0 +1,86 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "hl1_basecombatweapon_shared.h"
+#include "effect_dispatch_data.h"
+#include "te_effect_dispatch.h"
+
+
+BEGIN_DATADESC( CBaseHL1CombatWeapon )
+ DEFINE_THINKFUNC( FallThink ),
+END_DATADESC();
+
+
+void CBaseHL1CombatWeapon::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "BaseCombatWeapon.WeaponDrop" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CBaseHL1CombatWeapon::FallInit( void )
+{
+ SetModel( GetWorldModel() );
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_TRIGGER );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ SetPickupTouch();
+
+ SetThink( &CBaseHL1CombatWeapon::FallThink );
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ // HACKHACK - On ground isn't always set, so look for ground underneath
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector(0,0,2), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0 )
+ {
+ SetGroundEntity( tr.m_pEnt );
+ }
+
+ SetViewOffset( Vector(0,0,8) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Items that have just spawned run this think to catch them when
+// they hit the ground. Once we're sure that the object is grounded,
+// we change its solid type to trigger and set it in a large box that
+// helps the player get it.
+//-----------------------------------------------------------------------------
+void CBaseHL1CombatWeapon::FallThink ( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ // clatter if we have an owner (i.e., dropped by someone)
+ // don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!)
+ if ( GetOwnerEntity() )
+ {
+ EmitSound( "BaseCombatWeapon.WeaponDrop" );
+ }
+
+ // lie flat
+ QAngle ang = GetAbsAngles();
+ ang.x = 0;
+ ang.z = 0;
+ SetAbsAngles( ang );
+
+ Materialize();
+
+ SetSize( Vector( -24, -24, 0 ), Vector( 24, 24, 16 ) );
+ }
+}
+
diff --git a/game/server/hl1/hl1_basegrenade.cpp b/game/server/hl1/hl1_basegrenade.cpp
new file mode 100644
index 0000000..8762ae4
--- /dev/null
+++ b/game/server/hl1/hl1_basegrenade.cpp
@@ -0,0 +1,116 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "decals.h"
+#include "basecombatcharacter.h"
+#include "shake.h"
+#include "engine/IEngineSound.h"
+#include "soundent.h"
+#include "entitylist.h"
+#include "hl1_basegrenade.h"
+
+
+extern short g_sModelIndexFireball; // (in combatweapon.cpp) holds the index for the fireball
+extern short g_sModelIndexWExplosion; // (in combatweapon.cpp) holds the index for the underwater explosion
+
+unsigned int CHL1BaseGrenade::PhysicsSolidMaskForEntity( void ) const
+{
+ return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHL1BaseGrenade::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "BaseGrenade.Explode" );
+}
+
+
+void CHL1BaseGrenade::Explode( trace_t *pTrace, int bitsDamageType )
+{
+ float flRndSound;// sound randomizer
+
+ SetModelName( NULL_STRING );//invisible
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ m_takedamage = DAMAGE_NO;
+
+ // Pull out of the wall a bit
+ if ( pTrace->fraction != 1.0 )
+ {
+ SetLocalOrigin( pTrace->endpos + (pTrace->plane.normal * 0.6) );
+ }
+
+ Vector vecAbsOrigin = GetAbsOrigin();
+ int contents = UTIL_PointContents ( vecAbsOrigin );
+
+ if ( pTrace->fraction != 1.0 )
+ {
+ Vector vecNormal = pTrace->plane.normal;
+ const surfacedata_t *pdata = physprops->GetSurfaceData( pTrace->surface.surfaceProps );
+ CPASFilter filter( vecAbsOrigin );
+ te->Explosion( filter, 0.0,
+ &vecAbsOrigin,
+ !( contents & MASK_WATER ) ? g_sModelIndexFireball : g_sModelIndexWExplosion,
+ m_DmgRadius * .03,
+ 25,
+ TE_EXPLFLAG_NONE,
+ m_DmgRadius,
+ m_flDamage,
+ &vecNormal,
+ (char) pdata->game.material );
+ }
+ else
+ {
+ CPASFilter filter( vecAbsOrigin );
+ te->Explosion( filter, 0.0,
+ &vecAbsOrigin,
+ !( contents & MASK_WATER ) ? g_sModelIndexFireball : g_sModelIndexWExplosion,
+ m_DmgRadius * .03,
+ 25,
+ TE_EXPLFLAG_NONE,
+ m_DmgRadius,
+ m_flDamage );
+ }
+
+ CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), BASEGRENADE_EXPLOSION_VOLUME, 3.0 );
+
+ // Use the owner's position as the reported position
+ Vector vecReported = GetThrower() ? GetThrower()->GetAbsOrigin() : vec3_origin;
+
+ CTakeDamageInfo info( this, GetThrower(), GetBlastForce(), GetAbsOrigin(), m_flDamage, bitsDamageType, 0, &vecReported );
+
+ RadiusDamage( info, GetAbsOrigin(), m_DmgRadius, CLASS_NONE, NULL );
+
+ UTIL_DecalTrace( pTrace, "Scorch" );
+
+ flRndSound = random->RandomFloat( 0 , 1 );
+
+ EmitSound( "BaseGrenade.Explode" );
+
+ SetTouch( NULL );
+
+ AddEffects( EF_NODRAW );
+ SetAbsVelocity( vec3_origin );
+
+ SetThink( &CBaseGrenade::Smoke );
+ SetNextThink( gpGlobals->curtime + 0.3);
+
+ if ( GetWaterLevel() == 0 )
+ {
+ int sparkCount = random->RandomInt( 0,3 );
+ QAngle angles;
+ VectorAngles( pTrace->plane.normal, angles );
+
+ for ( int i = 0; i < sparkCount; i++ )
+ Create( "spark_shower", GetAbsOrigin(), angles, NULL );
+ }
+}
diff --git a/game/server/hl1/hl1_basegrenade.h b/game/server/hl1/hl1_basegrenade.h
new file mode 100644
index 0000000..1f88846
--- /dev/null
+++ b/game/server/hl1/hl1_basegrenade.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef HL1_BASEGRENADE_H
+#define HL1_BASEGRENADE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "basegrenade_shared.h"
+
+
+class CHL1BaseGrenade : public CBaseGrenade
+{
+ DECLARE_CLASS( CHL1BaseGrenade, CBaseGrenade );
+public:
+
+ virtual void Precache();
+
+ void Explode( trace_t *pTrace, int bitsDamageType );
+ unsigned int PhysicsSolidMaskForEntity( void ) const;
+};
+
+class CHandGrenade : public CHL1BaseGrenade
+{
+public:
+ DECLARE_CLASS( CHandGrenade, CHL1BaseGrenade );
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+ void BounceSound( void );
+ void BounceTouch( CBaseEntity *pOther );
+
+ void ShootTimed( CBaseCombatCharacter *pOwner, Vector vecVelocity, float flTime );
+};
+
+#endif // HL1_BASEGRENADE_H
diff --git a/game/server/hl1/hl1_client.cpp b/game/server/hl1/hl1_client.cpp
new file mode 100644
index 0000000..7473e10
--- /dev/null
+++ b/game/server/hl1/hl1_client.cpp
@@ -0,0 +1,199 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+/*
+
+===== tf_client.cpp ========================================================
+
+ HL1 client/server game specific stuff
+
+*/
+
+#include "cbase.h"
+#include "hl1_player.h"
+#include "hl1mp_player.h"
+#include "hl1_gamerules.h"
+#include "gamerules.h"
+#include "teamplay_gamerules.h"
+#include "entitylist.h"
+#include "physics.h"
+#include "game.h"
+#include "player_resource.h"
+#include "engine/IEngineSound.h"
+
+#include "tier0/vprof.h"
+
+void Host_Say( edict_t *pEdict, bool teamonly );
+
+extern CBaseEntity* FindPickerEntityClass( CBasePlayer *pPlayer, char *classname );
+extern bool g_fGameOver;
+
+/*
+===========
+ClientPutInServer
+
+called each time a player is spawned into the game
+============
+*/
+void ClientPutInServer( edict_t *pEdict, const char *playername )
+{
+ CHL1_Player *pPlayer = NULL;
+
+ // Allocate a CBasePlayer for pev, and call spawn
+ if ( g_pGameRules->IsMultiplayer() )
+ pPlayer = CHL1_Player::CreatePlayer( "player_mp", pEdict );
+ else
+ pPlayer = CHL1_Player::CreatePlayer( "player", pEdict );
+
+ pPlayer->SetPlayerName( playername );
+}
+
+
+void ClientActive( edict_t *pEdict, bool bLoadGame )
+{
+ CHL1_Player *pPlayer = dynamic_cast< CHL1_Player* >( CBaseEntity::Instance( pEdict ) );
+
+ pPlayer->InitialSpawn();
+
+ if ( !bLoadGame )
+ {
+ pPlayer->Spawn();
+ }
+}
+
+
+/*
+===============
+const char *GetGameDescription()
+
+Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2
+===============
+*/
+const char *GetGameDescription()
+{
+ if ( g_pGameRules ) // this function may be called before the world has spawned, and the game rules initialized
+ return g_pGameRules->GetGameDescription();
+ else
+ return "Half-Life 1";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Given a player and optional name returns the entity of that
+// classname that the player is nearest facing
+//
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+CBaseEntity* FindEntity( edict_t *pEdict, char *classname)
+{
+ // If no name was given set bits based on the picked
+ if (FStrEq(classname,""))
+ {
+ return (FindPickerEntityClass( static_cast<CBasePlayer*>(GetContainingEntity(pEdict)), classname ));
+ }
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache game-specific models & sounds
+//-----------------------------------------------------------------------------
+void ClientGamePrecache( void )
+{
+ // Multiplayer uses different models, and more of them.
+ if ( g_pGameRules->IsMultiplayer() )
+ {
+ CBaseEntity::PrecacheModel("models/player/mp/barney/barney.mdl");
+ CBaseEntity::PrecacheModel("models/player/mp/gina/gina.mdl");
+ CBaseEntity::PrecacheModel("models/player/mp/gman/gman.mdl");
+ CBaseEntity::PrecacheModel("models/player/mp/gordon/gordon.mdl");
+ CBaseEntity::PrecacheModel("models/player/mp/helmet/helmet.mdl");
+ CBaseEntity::PrecacheModel("models/player/mp/hgrunt/hgrunt.mdl");
+ CBaseEntity::PrecacheModel("models/player/mp/robo/robo.mdl");
+ CBaseEntity::PrecacheModel("models/player/mp/scientist/scientist.mdl");
+ CBaseEntity::PrecacheModel("models/player/mp/zombie/zombie.mdl");
+ CBaseEntity::PrecacheModel("models/player.mdl" );
+ }
+ else
+ {
+ CBaseEntity::PrecacheModel("models/player.mdl" );
+ }
+
+ CBaseEntity::PrecacheModel( "models/gibs/agibs.mdl" );
+
+ CBaseEntity::PrecacheScriptSound( "Player.UseDeny" );
+}
+
+
+// called by ClientKill and DeadThink
+void respawn( CBaseEntity *pEdict, bool fCopyCorpse )
+{
+ if (gpGlobals->coop || gpGlobals->deathmatch)
+ {
+ if ( fCopyCorpse )
+ {
+ // make a copy of the dead body for appearances sake
+ ((CHL1MP_Player *)pEdict)->CreateCorpse();
+ }
+
+ // respawn player
+ pEdict->Spawn();
+ }
+ else
+ { // restart the entire server
+ engine->ServerCommand("reload\n");
+ }
+}
+
+void GameStartFrame( void )
+{
+ VPROF("GameStartFrame()");
+
+ if ( g_fGameOver )
+ return;
+
+ gpGlobals->teamplay = (teamplay.GetInt() != 0);
+
+#ifdef DEBUG
+ extern void Bot_RunAll();
+ Bot_RunAll();
+#endif
+}
+
+//=========================================================
+// instantiate the proper game rules object
+//=========================================================
+void InstallGameRules()
+{
+ engine->ServerCommand( "exec game.cfg\n" );
+ engine->ServerExecute( );
+
+ if ( !gpGlobals->deathmatch )
+ {
+ // generic half-life
+ CreateGameRulesObject( "CHalfLife1" );
+ return;
+ }
+ else
+ {
+ CreateGameRulesObject( "CHL1MPRules" );
+ return;
+
+ if ( teamplay.GetInt() > 0 )
+ {
+ // teamplay
+ CreateGameRulesObject( "CTeamplayRules" );
+ return;
+ }
+
+ // vanilla deathmatch
+ CreateGameRulesObject( "CMultiplayRules" );
+ return;
+ }
+
+ CreateGameRulesObject( "CHalfLife1" );
+}
+
diff --git a/game/server/hl1/hl1_ents.cpp b/game/server/hl1/hl1_ents.cpp
new file mode 100644
index 0000000..c5ab56e
--- /dev/null
+++ b/game/server/hl1/hl1_ents.cpp
@@ -0,0 +1,1654 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "trains.h"
+#include "ndebugoverlay.h"
+#include "entitylist.h"
+#include "engine/IEngineSound.h"
+#include "hl1_ents.h"
+#include "doors.h"
+#include "soundent.h"
+#include "hl1_basegrenade.h"
+#include "shake.h"
+#include "globalstate.h"
+#include "soundscape.h"
+#include "buttons.h"
+#include "Sprite.h"
+#include "actanimating.h"
+#include "npcevent.h"
+#include "func_break.h"
+#include "hl1_shareddefs.h"
+#include "eventqueue.h"
+
+
+//
+// TRIGGERS: trigger_auto, trigger_relay, multimanager: replaced in src by logic_auto and logic_relay
+//
+
+// This trigger will fire when the level spawns (or respawns if not fire once)
+// It will check a global state before firing.
+#define SF_AUTO_FIREONCE 0x0001
+
+class CAutoTrigger : public CBaseEntity
+{
+ DECLARE_CLASS( CAutoTrigger, CBaseEntity );
+public:
+ void Spawn( void );
+ void Precache( void );
+ void Think( void );
+
+ int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
+
+ DECLARE_DATADESC();
+
+private:
+
+ COutputEvent m_OnTrigger;
+ string_t m_globalstate;
+};
+LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger );
+
+BEGIN_DATADESC( CAutoTrigger )
+ DEFINE_KEYFIELD( m_globalstate, FIELD_STRING, "globalstate" ),
+
+ // Outputs
+ DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"),
+END_DATADESC()
+
+
+void CAutoTrigger::Spawn( void )
+{
+ Precache();
+}
+
+
+void CAutoTrigger::Precache( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks the global state and fires targets if the global state is set.
+//-----------------------------------------------------------------------------
+void CAutoTrigger::Think( void )
+{
+ if ( !m_globalstate || GlobalEntity_GetState( m_globalstate ) == GLOBAL_ON )
+ {
+ m_OnTrigger.FireOutput(NULL, this);
+
+ if ( m_spawnflags & SF_AUTO_FIREONCE )
+ UTIL_Remove( this );
+ }
+}
+
+
+#define SF_RELAY_FIREONCE 0x0001
+
+class CTriggerRelay : public CBaseEntity
+{
+ DECLARE_CLASS( CTriggerRelay, CBaseEntity );
+public:
+
+ CTriggerRelay( void );
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ void Spawn( void );
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ void RefireThink( void );
+
+ int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
+
+ DECLARE_DATADESC();
+
+private:
+ USE_TYPE triggerType;
+ float m_flRefireInterval;
+ float m_flRefireDuration;
+ float m_flTimeRefireDone;
+
+ USE_TYPE m_TargetUseType;
+ float m_flTargetValue;
+
+ COutputEvent m_OnTrigger;
+};
+LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay );
+
+BEGIN_DATADESC( CTriggerRelay )
+ DEFINE_FIELD( triggerType, FIELD_INTEGER ),
+ DEFINE_KEYFIELD( m_flRefireInterval, FIELD_FLOAT, "repeatinterval" ),
+ DEFINE_KEYFIELD( m_flRefireDuration, FIELD_FLOAT, "repeatduration" ),
+ DEFINE_FIELD( m_flTimeRefireDone, FIELD_TIME ),
+ DEFINE_FIELD( m_TargetUseType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flTargetValue, FIELD_FLOAT ),
+
+ // Function Pointers
+ DEFINE_FUNCTION( RefireThink ),
+
+ // Outputs
+ DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"),
+END_DATADESC()
+
+CTriggerRelay::CTriggerRelay( void )
+{
+ m_flRefireInterval = -1;
+ m_flRefireDuration = -1;
+ m_flTimeRefireDone = -1;
+}
+
+bool CTriggerRelay::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if (FStrEq(szKeyName, "triggerstate"))
+ {
+ int type = atoi( szValue );
+ switch( type )
+ {
+ case 0:
+ triggerType = USE_OFF;
+ break;
+ case 2:
+ triggerType = USE_TOGGLE;
+ break;
+ default:
+ triggerType = USE_ON;
+ break;
+ }
+ }
+ else
+ {
+ return BaseClass::KeyValue( szKeyName, szValue );
+ }
+
+ return true;
+}
+
+
+void CTriggerRelay::Spawn( void )
+{
+}
+
+
+void CTriggerRelay::RefireThink( void )
+{
+ // sending this as Activator and Caller right now. Seems the safest thing
+ // since whatever fired the relay the first time may no longer exist.
+ Use( this, this, m_TargetUseType, m_flTargetValue );
+
+ if( gpGlobals->curtime > m_flTimeRefireDone )
+ {
+ UTIL_Remove( this );
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + m_flRefireInterval );
+ }
+}
+
+void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ m_OnTrigger.FireOutput(pActivator, this);
+
+ if ( m_spawnflags & SF_RELAY_FIREONCE )
+ {
+ UTIL_Remove( this );
+ }
+
+ else if( m_flRefireDuration != -1 && m_flTimeRefireDone == -1 )
+ {
+ // Set up to refire this target automatically
+ m_TargetUseType = useType;
+ m_flTargetValue = value;
+
+ m_flTimeRefireDone = gpGlobals->curtime + m_flRefireDuration;
+ SetThink( &CTriggerRelay::RefireThink );
+ SetNextThink( gpGlobals->curtime + m_flRefireInterval );
+ }
+}
+
+
+// The Multimanager Entity - when fired, will fire up to 16 targets
+// at specified times.
+
+class CMultiManager : public CPointEntity
+{
+ DECLARE_CLASS( CMultiManager, CPointEntity );
+public:
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ void Spawn ( void );
+ void ManagerThink ( void );
+ void ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+#if _DEBUG
+ void ManagerReport( void );
+#endif
+
+ bool HasTarget( string_t targetname );
+
+ int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
+
+ DECLARE_DATADESC();
+
+ int m_cTargets; // the total number of targets in this manager's fire list.
+ int m_index; // Current target
+ float m_flWait;
+ EHANDLE m_hActivator;
+ float m_startTime;// Time we started firing
+ string_t m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array
+ float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire
+
+ COutputEvent m_OnTrigger;
+
+ void InputManagerTrigger( inputdata_t &data );
+};
+LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager );
+
+// Global Savedata for multi_manager
+BEGIN_DATADESC( CMultiManager )
+ DEFINE_KEYFIELD( m_flWait, FIELD_FLOAT, "wait" ),
+
+ DEFINE_FIELD( m_cTargets, FIELD_INTEGER ),
+ DEFINE_FIELD( m_index, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_startTime, FIELD_TIME ),
+ DEFINE_ARRAY( m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ),
+ DEFINE_ARRAY( m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ),
+
+ // Function Pointers
+ DEFINE_FUNCTION( ManagerThink ),
+ DEFINE_FUNCTION( ManagerUse ),
+#if _DEBUG
+ DEFINE_FUNCTION( ManagerReport ),
+#endif
+
+ // Outputs
+ DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Trigger", InputManagerTrigger ),
+END_DATADESC()
+
+void CMultiManager::InputManagerTrigger( inputdata_t &data )
+{
+ ManagerUse ( NULL, NULL, USE_TOGGLE, 0 );
+}
+
+bool CMultiManager::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( BaseClass::KeyValue( szKeyName, szValue ) )
+ {
+ return true;
+ }
+ else // add this field to the target list
+ {
+ // this assumes that additional fields are targetnames and their values are delay values.
+ if ( m_cTargets < MAX_MULTI_TARGETS )
+ {
+ char tmp[128];
+
+ UTIL_StripToken( szKeyName, tmp, Q_ARRAYSIZE( tmp ) );
+ m_iTargetName [ m_cTargets ] = AllocPooledString( tmp );
+ m_flTargetDelay [ m_cTargets ] = atof (szValue);
+ m_cTargets++;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+void CMultiManager::Spawn( void )
+{
+ SetSolid( SOLID_NONE );
+ SetUse ( &CMultiManager::ManagerUse );
+ SetThink ( &CMultiManager::ManagerThink);
+
+ // Sort targets
+ // Quick and dirty bubble sort
+ int swapped = 1;
+
+ while ( swapped )
+ {
+ swapped = 0;
+ for ( int i = 1; i < m_cTargets; i++ )
+ {
+ if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] )
+ {
+ // Swap out of order elements
+ string_t name = m_iTargetName[i];
+ float delay = m_flTargetDelay[i];
+ m_iTargetName[i] = m_iTargetName[i-1];
+ m_flTargetDelay[i] = m_flTargetDelay[i-1];
+ m_iTargetName[i-1] = name;
+ m_flTargetDelay[i-1] = delay;
+ swapped = 1;
+ }
+ }
+ }
+}
+
+
+bool CMultiManager::HasTarget( string_t targetname )
+{
+ for ( int i = 0; i < m_cTargets; i++ )
+ if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) )
+ return true;
+
+ return false;
+}
+
+
+// Designers were using this to fire targets that may or may not exist --
+// so I changed it to use the standard target fire code, made it a little simpler.
+void CMultiManager::ManagerThink ( void )
+{
+ float t;
+
+ t = gpGlobals->curtime - m_startTime;
+
+ while ( m_index < m_cTargets && m_flTargetDelay[ m_index ] <= t )
+ {
+ FireTargets( STRING( m_iTargetName[ m_index ] ), m_hActivator, this, USE_TOGGLE, 0 );
+ m_index++;
+ }
+
+ if ( m_index >= m_cTargets )// have we fired all targets?
+ {
+ SetThink( NULL );
+ SetUse ( &CMultiManager::ManagerUse );// allow manager re-use
+ }
+ else
+ {
+ SetNextThink( m_startTime + m_flTargetDelay[ m_index ] );
+ }
+}
+
+
+// The USE function builds the time table and starts the entity thinking.
+void CMultiManager::ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ m_hActivator = pActivator;
+ m_index = 0;
+ m_startTime = gpGlobals->curtime;
+
+ m_OnTrigger.FireOutput(pActivator, this);
+
+ // Calculate the time to re-enable the multimanager - just after the last output is fired.
+ // dvsents2: need to disable multimanager until last output is fired
+ //m_fEnableTime = gpGlobals->curtime + m_OnTrigger.GetMaxDelay();
+
+ SetUse( NULL );// disable use until all targets have fired
+
+ SetThink ( &CMultiManager::ManagerThink );
+ SetNextThink( gpGlobals->curtime );
+}
+
+
+#if _DEBUG
+void CMultiManager::ManagerReport ( void )
+{
+ int cIndex;
+
+ for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ )
+ {
+ Msg( "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] );
+ }
+}
+#endif
+
+
+//
+// Pendulum
+//
+
+#define SF_PENDULUM_SWING 2
+
+LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum );
+
+BEGIN_DATADESC( CPendulum )
+ DEFINE_FIELD( m_flAccel, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flDampSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vCenter, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vStart, FIELD_VECTOR ),
+
+ DEFINE_KEYFIELD( m_flMoveDistance, FIELD_FLOAT, "pendistance" ),
+ DEFINE_KEYFIELD( m_flDamp, FIELD_FLOAT, "damp" ),
+ DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ),
+
+ DEFINE_FUNCTION( PendulumUse ),
+ DEFINE_FUNCTION( Swing ),
+ DEFINE_FUNCTION( Stop ),
+ DEFINE_FUNCTION( RopeTouch ),
+
+ DEFINE_FIELD( m_hEnemy, FIELD_EHANDLE ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
+END_DATADESC()
+
+void CPendulum::Spawn( void )
+{
+ CBaseToggle::AxisDir();
+
+ m_flDamp *= 0.001;
+
+ if ( FBitSet ( m_spawnflags, SF_DOOR_PASSABLE ) )
+ SetSolid( SOLID_NONE );
+ else
+ SetSolid( SOLID_BBOX );
+
+ SetMoveType( MOVETYPE_PUSH );
+ SetModel( STRING(GetModelName()) );
+
+ if ( m_flMoveDistance != 0 )
+ {
+ if ( m_flSpeed == 0 )
+ m_flSpeed = 100;
+
+ m_flAccel = ( m_flSpeed * m_flSpeed ) / ( 2 * fabs( m_flMoveDistance )); // Calculate constant acceleration from speed and distance
+ m_flMaxSpeed = m_flSpeed;
+ m_vStart = GetAbsAngles();
+ m_vCenter = GetAbsAngles() + ( m_flMoveDistance * 0.05 ) * m_vecMoveAng;
+
+ if ( FBitSet( m_spawnflags, SF_BRUSH_ROTATE_START_ON ) )
+ {
+ SetThink( &CBaseEntity::SUB_CallUseToggle );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+
+ m_flSpeed = 0;
+ SetUse( &CPendulum::PendulumUse );
+
+ VPhysicsInitShadow( false, false );
+ ///VPhysicsGetObject()->SetPosition( GetAbsOrigin(), pev->absangles );
+ }
+
+ if ( FBitSet( m_spawnflags, SF_PENDULUM_SWING ) )
+ {
+ SetTouch ( &CPendulum::RopeTouch );
+ }
+}
+
+
+void CPendulum::PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( m_flSpeed ) // Pendulum is moving, stop it and auto-return if necessary
+ {
+ if ( FBitSet( m_spawnflags, SF_BRUSH_ROTATE_START_ON ) )
+ {
+ float delta;
+
+ delta = CBaseToggle::AxisDelta( m_spawnflags, GetAbsAngles(), m_vStart );
+
+ SetLocalAngularVelocity( m_flMaxSpeed * m_vecMoveAng );
+ SetNextThink( gpGlobals->curtime + delta / m_flMaxSpeed);
+ SetThink( &CPendulum::Stop );
+ }
+ else
+ {
+ m_flSpeed = 0; // Dead stop
+ SetThink( NULL );
+ SetLocalAngularVelocity( QAngle( 0, 0, 0 ) );
+ }
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 0.1f ); // Start the pendulum moving
+ m_flTime = gpGlobals->curtime; // Save time to calculate dt
+ SetThink( &CPendulum::Swing );
+ m_flDampSpeed = m_flMaxSpeed;
+ }
+}
+
+void CPendulum::InputActivate( inputdata_t &inputdata )
+{
+ SetNextThink( gpGlobals->curtime + 0.1f ); // Start the pendulum moving
+ m_flTime = gpGlobals->curtime; // Save time to calculate dt
+ SetThink( &CPendulum::Swing );
+ m_flDampSpeed = m_flMaxSpeed;
+}
+
+void CPendulum::Stop( void )
+{
+ SetAbsAngles( m_vStart );
+ m_flSpeed = 0;
+ SetThink( NULL );
+ SetLocalAngularVelocity( QAngle ( 0, 0, 0 ) );
+}
+
+
+void CPendulum::Blocked( CBaseEntity *pOther )
+{
+ m_flTime = gpGlobals->curtime;
+}
+
+void CPendulum::Swing( void )
+{
+ float delta, dt;
+
+ delta = CBaseToggle::AxisDelta( m_spawnflags, GetAbsAngles(), m_vCenter );
+ dt = gpGlobals->curtime - m_flTime; // How much time has passed?
+ m_flTime = gpGlobals->curtime; // Remember the last time called
+
+ if ( delta > 0 && m_flAccel > 0 )
+ m_flSpeed -= m_flAccel * dt; // Integrate velocity
+ else
+ m_flSpeed += m_flAccel * dt;
+
+ if ( m_flSpeed > m_flMaxSpeed )
+ m_flSpeed = m_flMaxSpeed;
+ else if ( m_flSpeed < -m_flMaxSpeed )
+ m_flSpeed = -m_flMaxSpeed;
+
+ // scale the destdelta vector by the time spent traveling to get velocity
+ SetLocalAngularVelocity( m_flSpeed * m_vecMoveAng );
+
+ // Call this again
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ SetMoveDoneTime( 0.1 );
+
+ if ( m_flDamp )
+ {
+ m_flDampSpeed -= m_flDamp * m_flDampSpeed * dt;
+ if ( m_flDampSpeed < 30.0 )
+ {
+ SetAbsAngles( m_vCenter );
+ m_flSpeed = 0;
+ SetThink( NULL );
+ SetLocalAngularVelocity( QAngle( 0, 0, 0 ) );
+ }
+ else if ( m_flSpeed > m_flDampSpeed )
+ m_flSpeed = m_flDampSpeed;
+ else if ( m_flSpeed < -m_flDampSpeed )
+ m_flSpeed = -m_flDampSpeed;
+ }
+}
+
+void CPendulum::Touch ( CBaseEntity *pOther )
+{
+ if ( m_flBlockDamage <= 0 )
+ return;
+
+ // we can't hurt this thing, so we're not concerned with it
+ if ( !pOther->m_takedamage )
+ return;
+
+ // calculate damage based on rotation speed
+ float damage = m_flBlockDamage * m_flSpeed * 0.01;
+
+ if ( damage < 0 )
+ damage = -damage;
+
+ pOther->TakeDamage( CTakeDamageInfo( this, this, damage, DMG_CRUSH ) );
+
+ Vector vNewVel = (pOther->GetAbsOrigin() - GetAbsOrigin());
+
+ VectorNormalize( vNewVel );
+
+ pOther->SetAbsVelocity( vNewVel * damage );
+}
+
+void CPendulum::RopeTouch ( CBaseEntity *pOther )
+{
+ if ( !pOther->IsPlayer() )
+ {// not a player!
+ DevMsg ( 2, "Not a client\n" );
+ return;
+ }
+
+ if ( pOther == GetEnemy() )
+ return;
+
+ m_hEnemy = pOther;
+ pOther->SetAbsVelocity( Vector ( 0, 0, 0 ) );
+ pOther->SetMoveType( MOVETYPE_NONE );
+}
+
+
+//
+// MORTARS
+//
+
+class CFuncMortarField : public CBaseToggle
+{
+ DECLARE_CLASS( CFuncMortarField, CBaseToggle );
+public:
+ void Spawn( void );
+ void Precache( void );
+ void KeyValue( KeyValueData *pkvd );
+
+ // Bmodels don't go across transitions
+ virtual int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
+
+/* virtual int Save( CSave &save );
+ virtual int Restore( CRestore &restore );
+
+ static TYPEDESCRIPTION m_SaveData[];*/
+
+ //void EXPORT FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+ void InputTrigger ( inputdata_t &inputdata );
+
+ DECLARE_DATADESC();
+
+ string_t m_iszXController;
+ string_t m_iszYController;
+ float m_flSpread;
+ int m_iCount;
+ int m_fControl;
+};
+
+LINK_ENTITY_TO_CLASS( func_mortar_field, CFuncMortarField );
+
+BEGIN_DATADESC( CFuncMortarField )
+ DEFINE_KEYFIELD( m_iszXController, FIELD_STRING, "m_iszXController" ),
+ DEFINE_KEYFIELD( m_iszYController, FIELD_STRING, "m_iszYController" ),
+ DEFINE_KEYFIELD( m_flSpread, FIELD_FLOAT, "m_flSpread" ),
+ DEFINE_KEYFIELD( m_iCount, FIELD_INTEGER, "m_iCount" ),
+ DEFINE_KEYFIELD( m_fControl, FIELD_INTEGER, "m_fControl" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Trigger", InputTrigger ),
+END_DATADESC()
+
+
+// Drop bombs from above
+void CFuncMortarField::Spawn( void )
+{
+ SetSolid( SOLID_NONE );
+ SetModel( STRING(GetModelName()) ); // set size and link into world
+ SetMoveType( MOVETYPE_NONE );
+ AddEffects( EF_NODRAW );
+// SetUse( FieldUse );
+ Precache();
+}
+
+
+void CFuncMortarField::Precache( void )
+{
+ PrecacheModel( "sprites/lgtning.vmt" );
+
+ PrecacheScriptSound( "MortarField.Trigger" );
+}
+
+void CFuncMortarField::InputTrigger( inputdata_t &inputdata )
+{
+ Vector vecStart;
+ CollisionProp()->RandomPointInBounds( Vector( 0, 0, 1 ), Vector( 1, 1, 1 ), &vecStart );
+
+ switch( m_fControl )
+ {
+ case 0: // random
+ break;
+ case 1: // Trigger Activator
+ if (inputdata.pActivator != NULL)
+ {
+ vecStart.x = inputdata.pActivator->GetAbsOrigin().x;
+ vecStart.y = inputdata.pActivator->GetAbsOrigin().y;
+ }
+ break;
+ case 2: // table
+ {
+ CBaseEntity *pController;
+
+ if ( m_iszXController != NULL_STRING )
+ {
+ pController = gEntList.FindEntityByName( NULL, STRING(m_iszXController) );
+ if (pController != NULL)
+ {
+ if ( FClassnameIs( pController, "momentary_rot_button" ) )
+ {
+ CMomentaryRotButton *pXController = static_cast<CMomentaryRotButton*>( pController );
+ Vector vecNormalizedPos( pXController->GetPos( pXController->GetLocalAngles() ), 0.0f, 0.0f );
+ Vector vecWorldSpace;
+ CollisionProp()->NormalizedToWorldSpace( vecNormalizedPos, &vecWorldSpace );
+ vecStart.x = vecWorldSpace.x;
+ }
+ else
+ {
+ DevMsg( "func_mortarfield has X controller that isn't a momentary_rot_button.\n" );
+ }
+ }
+ }
+ if ( m_iszYController != NULL_STRING )
+ {
+ pController = gEntList.FindEntityByName( NULL, STRING(m_iszYController) );
+ if (pController != NULL)
+ {
+ if ( FClassnameIs( pController, "momentary_rot_button" ) )
+ {
+ CMomentaryRotButton *pYController = static_cast<CMomentaryRotButton*>( pController );
+ Vector vecNormalizedPos( 0.0f, pYController->GetPos( pYController->GetLocalAngles() ), 0.0f );
+ Vector vecWorldSpace;
+ CollisionProp()->NormalizedToWorldSpace( vecNormalizedPos, &vecWorldSpace );
+ vecStart.y = vecWorldSpace.y;
+ }
+ else
+ {
+ DevMsg( "func_mortarfield has Y controller that isn't a momentary_rot_button.\n" );
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ CPASAttenuationFilter filter( this, ATTN_NONE );
+ EmitSound( filter, entindex(), "MortarField.Trigger" );
+
+ float t = 2.5;
+ for (int i = 0; i < m_iCount; i++)
+ {
+ Vector vecSpot = vecStart;
+ vecSpot.x += random->RandomFloat( -m_flSpread, m_flSpread );
+ vecSpot.y += random->RandomFloat( -m_flSpread, m_flSpread );
+
+ trace_t tr;
+ UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -1 ) * MAX_TRACE_LENGTH, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ CBaseEntity *pMortar = Create( "monster_mortar", tr.endpos, QAngle( 0, 0, 0 ), inputdata.pActivator );
+ pMortar->SetNextThink( gpGlobals->curtime + t );
+ t += random->RandomFloat( 0.2, 0.5 );
+
+ if (i == 0)
+ CSoundEnt::InsertSound ( SOUND_DANGER, tr.endpos, 400, 0.3 );
+ }
+}
+
+#ifdef HL1_DLL
+
+class CMortar : public CHL1BaseGrenade
+{
+ DECLARE_CLASS( CMortar, CHL1BaseGrenade );
+
+public:
+ void Spawn( void );
+ void Precache( void );
+
+ void MortarExplode( void );
+
+ int m_spriteTexture;
+
+ DECLARE_DATADESC();
+};
+
+BEGIN_DATADESC( CMortar )
+ DEFINE_THINKFUNC( MortarExplode ),
+ //DEFINE_FIELD( m_spriteTexture, FIELD_INTEGER ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( monster_mortar, CMortar );
+
+void CMortar::Spawn( )
+{
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_NONE );
+
+ SetDamage( 200 );
+ SetDamageRadius( GetDamage() * 2.5 );
+
+ SetThink( &CMortar::MortarExplode );
+ SetNextThink( TICK_NEVER_THINK );
+
+ Precache( );
+}
+
+
+void CMortar::Precache( )
+{
+ m_spriteTexture = PrecacheModel( "sprites/lgtning.vmt" );
+}
+
+void CMortar::MortarExplode( void )
+{
+ Vector vecStart = GetAbsOrigin();
+ Vector vecEnd = vecStart;
+ vecEnd.z += 1024;
+
+ UTIL_Beam( vecStart, vecEnd, m_spriteTexture, 0, 0, 0, 0.5, 4.0, 4.0, 100, 0, 255, 160, 100, 128, 0 );
+
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin() + Vector( 0, 0, 1024 ), GetAbsOrigin() - Vector( 0, 0, 1024 ), MASK_ALL, this, COLLISION_GROUP_NONE, &tr );
+
+
+ Explode( &tr, DMG_BLAST | DMG_MISSILEDEFENSE );
+ UTIL_ScreenShake( tr.endpos, 25.0, 150.0, 1.0, 750, SHAKE_START );
+}
+
+#endif
+
+//=========================================================
+// Dead HEV suit prop
+//=========================================================
+class CNPC_DeadHEV : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_DeadHEV, CAI_BaseNPC );
+public:
+ void Spawn( void );
+ Class_T Classify ( void ) { return CLASS_NONE; }
+ float MaxYawSpeed( void ) { return 8.0f; }
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+
+ int m_iPose;// which sequence to display -- temporary, don't need to save
+ static char *m_szPoses[4];
+};
+
+char *CNPC_DeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" };
+
+bool CNPC_DeadHEV::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "pose" ) )
+ m_iPose = atoi( szValue );
+ else
+ CAI_BaseNPC::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CNPC_DeadHEV );
+
+//=========================================================
+// ********** DeadHEV SPAWN **********
+//=========================================================
+void CNPC_DeadHEV::Spawn( void )
+{
+ PrecacheModel("models/player.mdl");
+ SetModel( "models/player.mdl" );
+
+ ClearEffects();
+ SetSequence( 0 );
+ m_nBody = 1;
+ m_bloodColor = BLOOD_COLOR_RED;
+
+ SetSequence( LookupSequence( m_szPoses[m_iPose] ) );
+
+ if ( GetSequence() == -1 )
+ {
+ Msg ( "Dead hevsuit with bad pose\n" );
+ SetSequence( 0 );
+ ClearEffects();
+ AddEffects( EF_BRIGHTLIGHT );
+ }
+
+ // Corpses have less health
+ m_iHealth = 8;
+
+ NPCInitDead();
+}
+
+
+//
+// Render parameters trigger
+//
+// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt)
+// to its targets when triggered.
+//
+
+
+// Flags to indicate masking off various render parameters that are normally copied to the targets
+#define SF_RENDER_MASKFX (1<<0)
+#define SF_RENDER_MASKAMT (1<<1)
+#define SF_RENDER_MASKMODE (1<<2)
+#define SF_RENDER_MASKCOLOR (1<<3)
+
+class CRenderFxManager : public CBaseEntity
+{
+ DECLARE_CLASS( CRenderFxManager, CBaseEntity );
+public:
+ void Spawn( void );
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+ // Input handlers.
+ void InputActivate( inputdata_t &inputdata );
+
+ DECLARE_DATADESC();
+};
+
+BEGIN_DATADESC( CRenderFxManager )
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager );
+
+
+void CRenderFxManager::Spawn( void )
+{
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetMoveType( MOVETYPE_NONE );
+ AddEffects( EF_NODRAW );
+}
+
+
+void CRenderFxManager::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( m_target != NULL_STRING )
+ {
+ CBaseEntity *pEntity = NULL;
+ while ( ( pEntity = gEntList.FindEntityByName( pEntity, STRING( m_target ) ) ) != NULL )
+ {
+ if ( !HasSpawnFlags( SF_RENDER_MASKFX ) )
+ pEntity->m_nRenderFX = m_nRenderFX;
+ if ( !HasSpawnFlags( SF_RENDER_MASKAMT ) )
+ pEntity->SetRenderColorA( GetRenderColor().a );
+ if ( !HasSpawnFlags( SF_RENDER_MASKMODE ) )
+ pEntity->m_nRenderMode = m_nRenderMode;
+ if ( !HasSpawnFlags( SF_RENDER_MASKCOLOR ) )
+ pEntity->m_clrRender = m_clrRender;
+ }
+ }
+}
+
+
+void CRenderFxManager::InputActivate( inputdata_t &inputdata )
+{
+ Use( inputdata.pActivator, inputdata.pCaller, USE_ON, 0 );
+}
+
+
+// Link env_sound to soundscape system
+LINK_ENTITY_TO_CLASS( env_sound, CEnvSoundscape );
+
+
+///////////////////////
+//XEN!
+//////////////////////
+
+
+#define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr"
+#define XEN_PLANT_HIDE_TIME 5
+
+class CXenPLight : public CActAnimating
+{
+ DECLARE_CLASS( CXenPLight, CActAnimating );
+
+public:
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+ void Touch( CBaseEntity *pOther );
+ void Think( void );
+
+ void LightOn( void );
+ void LightOff( void );
+
+ float m_flDmgTime;
+
+
+private:
+ CSprite *m_pGlow;
+};
+
+LINK_ENTITY_TO_CLASS( xen_plantlight, CXenPLight );
+
+BEGIN_DATADESC( CXenPLight )
+ DEFINE_FIELD( m_pGlow, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_flDmgTime, FIELD_FLOAT ),
+END_DATADESC()
+
+void CXenPLight::Spawn( void )
+{
+ Precache();
+
+ SetModel( "models/light.mdl" );
+
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
+
+ UTIL_SetSize( this, Vector(-80,-80,0), Vector(80,80,32));
+ SetActivity( ACT_IDLE );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ SetCycle( random->RandomFloat(0,1) );
+
+ m_pGlow = CSprite::SpriteCreate( XEN_PLANT_GLOW_SPRITE, GetLocalOrigin() + Vector(0,0,(WorldAlignMins().z+WorldAlignMaxs().z)*0.5), FALSE );
+ m_pGlow->SetTransparency( kRenderGlow, GetRenderColor().r, GetRenderColor().g, GetRenderColor().b, GetRenderColor().a, m_nRenderFX );
+ m_pGlow->SetAttachment( this, 1 );
+}
+
+
+void CXenPLight::Precache( void )
+{
+ PrecacheModel( "models/light.mdl" );
+ PrecacheModel( XEN_PLANT_GLOW_SPRITE );
+}
+
+
+void CXenPLight::Think( void )
+{
+ StudioFrameAdvance();
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ switch( GetActivity() )
+ {
+ case ACT_CROUCH:
+ if ( IsSequenceFinished() )
+ {
+ SetActivity( ACT_CROUCHIDLE );
+ LightOff();
+ }
+ break;
+
+ case ACT_CROUCHIDLE:
+ if ( gpGlobals->curtime > m_flDmgTime )
+ {
+ SetActivity( ACT_STAND );
+ LightOn();
+ }
+ break;
+
+ case ACT_STAND:
+ if ( IsSequenceFinished() )
+ SetActivity( ACT_IDLE );
+ break;
+
+ case ACT_IDLE:
+ default:
+ break;
+ }
+}
+
+
+void CXenPLight::Touch( CBaseEntity *pOther )
+{
+ if ( pOther->IsPlayer() )
+ {
+ m_flDmgTime = gpGlobals->curtime + XEN_PLANT_HIDE_TIME;
+ if ( GetActivity() == ACT_IDLE || GetActivity() == ACT_STAND )
+ {
+ SetActivity( ACT_CROUCH );
+ }
+ }
+}
+
+
+void CXenPLight::LightOn( void )
+{
+ variant_t Value;
+ g_EventQueue.AddEvent( STRING( m_target ), "TurnOn", Value, 0, this, this );
+
+ if ( m_pGlow )
+ m_pGlow->RemoveEffects( EF_NODRAW );
+}
+
+
+void CXenPLight::LightOff( void )
+{
+ variant_t Value;
+ g_EventQueue.AddEvent( STRING( m_target ), "TurnOff", Value, 0, this, this );
+
+ if ( m_pGlow )
+ m_pGlow->AddEffects( EF_NODRAW );
+}
+
+
+class CXenHair : public CActAnimating
+{
+ DECLARE_CLASS( CXenHair, CActAnimating );
+public:
+ void Spawn( void );
+ void Precache( void );
+ void Think( void );
+};
+
+LINK_ENTITY_TO_CLASS( xen_hair, CXenHair );
+
+#define SF_HAIR_SYNC 0x0001
+
+void CXenHair::Spawn( void )
+{
+ Precache();
+ SetModel( "models/hair.mdl" );
+ UTIL_SetSize( this, Vector(-4,-4,0), Vector(4,4,32));
+ SetSequence( 0 );
+
+ if ( !HasSpawnFlags( SF_HAIR_SYNC ) )
+ {
+ SetCycle( random->RandomFloat( 0,1) );
+ m_flPlaybackRate = random->RandomFloat( 0.7, 1.4 );
+ }
+ ResetSequenceInfo( );
+
+ SetSolid( SOLID_NONE );
+ SetMoveType( MOVETYPE_NONE );
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.4 ) ); // Load balance these a bit
+}
+
+
+void CXenHair::Think( void )
+{
+ StudioFrameAdvance();
+ SetNextThink( gpGlobals->curtime + 0.1 );
+}
+
+
+void CXenHair::Precache( void )
+{
+ PrecacheModel( "models/hair.mdl" );
+}
+
+class CXenTreeTrigger : public CBaseEntity
+{
+ DECLARE_CLASS( CXenTreeTrigger, CBaseEntity );
+public:
+ void Touch( CBaseEntity *pOther );
+ static CXenTreeTrigger *TriggerCreate( CBaseEntity *pOwner, const Vector &position );
+};
+LINK_ENTITY_TO_CLASS( xen_ttrigger, CXenTreeTrigger );
+
+CXenTreeTrigger *CXenTreeTrigger::TriggerCreate( CBaseEntity *pOwner, const Vector &position )
+{
+ CXenTreeTrigger *pTrigger = CREATE_ENTITY( CXenTreeTrigger, "xen_ttrigger" );
+ pTrigger->SetAbsOrigin( position );
+
+ pTrigger->SetSolid( SOLID_BBOX );
+ pTrigger->AddSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
+ pTrigger->SetMoveType( MOVETYPE_NONE );
+ pTrigger->SetOwnerEntity( pOwner );
+
+ return pTrigger;
+}
+
+
+void CXenTreeTrigger::Touch( CBaseEntity *pOther )
+{
+ if ( GetOwnerEntity() )
+ {
+ GetOwnerEntity()->Touch( pOther );
+ }
+}
+
+#define TREE_AE_ATTACK 1
+
+class CXenTree : public CActAnimating
+{
+ DECLARE_CLASS( CXenTree, CActAnimating );
+public:
+ void Spawn( void );
+ void Precache( void );
+ void Touch( CBaseEntity *pOther );
+ void Think( void );
+ int OnTakeDamage( const CTakeDamageInfo &info ) { Attack(); return 0; }
+ void HandleAnimEvent( animevent_t *pEvent );
+ void Attack( void );
+ Class_T Classify( void ) { return CLASS_ALIEN_PREDATOR; }
+
+ DECLARE_DATADESC();
+
+private:
+ CXenTreeTrigger *m_pTrigger;
+};
+
+LINK_ENTITY_TO_CLASS( xen_tree, CXenTree );
+
+BEGIN_DATADESC( CXenTree )
+ DEFINE_FIELD( m_pTrigger, FIELD_CLASSPTR ),
+END_DATADESC()
+
+void CXenTree::Spawn( void )
+{
+ Precache();
+
+ SetModel( "models/tree.mdl" );
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid ( SOLID_BBOX );
+
+ m_takedamage = DAMAGE_YES;
+
+ UTIL_SetSize( this, Vector(-30,-30,0), Vector(30,30,188));
+ SetActivity( ACT_IDLE );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ SetCycle( random->RandomFloat( 0,1 ) );
+ m_flPlaybackRate = random->RandomFloat( 0.7, 1.4 );
+
+ Vector triggerPosition, vForward;
+
+ AngleVectors( GetAbsAngles(), &vForward );
+ triggerPosition = GetAbsOrigin() + (vForward * 64);
+
+ // Create the trigger
+ m_pTrigger = CXenTreeTrigger::TriggerCreate( this, triggerPosition );
+ UTIL_SetSize( m_pTrigger, Vector( -24, -24, 0 ), Vector( 24, 24, 128 ) );
+}
+
+void CXenTree::Precache( void )
+{
+ PrecacheModel( "models/tree.mdl" );
+ PrecacheModel( XEN_PLANT_GLOW_SPRITE );
+
+ PrecacheScriptSound( "XenTree.AttackMiss" );
+ PrecacheScriptSound( "XenTree.AttackHit" );
+}
+
+
+void CXenTree::Touch( CBaseEntity *pOther )
+{
+ if ( !pOther->IsPlayer() && FClassnameIs( pOther, "monster_bigmomma" ) )
+ return;
+
+ Attack();
+}
+
+
+void CXenTree::Attack( void )
+{
+ if ( GetActivity() == ACT_IDLE )
+ {
+ SetActivity( ACT_MELEE_ATTACK1 );
+ m_flPlaybackRate = random->RandomFloat( 1.0, 1.4 );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "XenTree.AttackMiss" );
+ }
+}
+
+
+void CXenTree::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case TREE_AE_ATTACK:
+ {
+ CBaseEntity *pList[8];
+ BOOL sound = FALSE;
+ int count = UTIL_EntitiesInBox( pList, 8, m_pTrigger->GetAbsOrigin() + m_pTrigger->WorldAlignMins(), m_pTrigger->GetAbsOrigin() + m_pTrigger->WorldAlignMaxs(), FL_NPC|FL_CLIENT );
+
+ Vector forward;
+ AngleVectors( GetAbsAngles(), &forward );
+
+ for ( int i = 0; i < count; i++ )
+ {
+ if ( pList[i] != this )
+ {
+ if ( pList[i]->GetOwnerEntity() != this )
+ {
+ sound = true;
+ pList[i]->TakeDamage( CTakeDamageInfo(this, this, 25, DMG_CRUSH | DMG_SLASH ) );
+ pList[i]->ViewPunch( QAngle( 15, 0, 18 ) );
+
+ pList[i]->SetAbsVelocity( pList[i]->GetAbsVelocity() + forward * 100 );
+ }
+ }
+ }
+
+ if ( sound )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "XenTree.AttackHit" );
+ }
+ }
+ return;
+ }
+
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+void CXenTree::Think( void )
+{
+ StudioFrameAdvance();
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ DispatchAnimEvents( this );
+
+ switch( GetActivity() )
+ {
+ case ACT_MELEE_ATTACK1:
+ if ( IsSequenceFinished() )
+ {
+ SetActivity( ACT_IDLE );
+ m_flPlaybackRate = random->RandomFloat( 0.6f, 1.4f );
+ }
+ break;
+
+ default:
+ case ACT_IDLE:
+ break;
+
+ }
+}
+
+class CXenSpore : public CActAnimating
+{
+ DECLARE_CLASS( CXenSpore, CActAnimating );
+public:
+ void Spawn( void );
+ void Precache( void );
+ void Touch( CBaseEntity *pOther );
+// void HandleAnimEvent( MonsterEvent_t *pEvent );
+ void Attack( void ) {}
+
+ static const char *pModelNames[];
+};
+
+class CXenSporeSmall : public CXenSpore
+{
+ DECLARE_CLASS( CXenSporeSmall, CXenSpore );
+ void Spawn( void );
+};
+
+class CXenSporeMed : public CXenSpore
+{
+ DECLARE_CLASS( CXenSporeMed, CXenSpore );
+ void Spawn( void );
+};
+
+class CXenSporeLarge : public CXenSpore
+{
+ DECLARE_CLASS( CXenSporeLarge, CXenSpore );
+ void Spawn( void );
+
+ static const Vector m_hullSizes[];
+};
+
+// Fake collision box for big spores
+class CXenHull : public CPointEntity
+{
+ DECLARE_CLASS( CXenHull, CPointEntity );
+public:
+ static CXenHull *CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset );
+ Class_T Classify( void ) { return CLASS_ALIEN_PREDATOR; }
+};
+
+CXenHull *CXenHull::CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset )
+{
+ CXenHull *pHull = CREATE_ENTITY( CXenHull, "xen_hull" );
+
+ UTIL_SetOrigin( pHull, source->GetAbsOrigin() + offset );
+ pHull->SetSolid( SOLID_BBOX );
+ pHull->SetMoveType( MOVETYPE_NONE );
+ pHull->SetOwnerEntity( source );
+ UTIL_SetSize( pHull, mins, maxs );
+ pHull->SetRenderColorA( 0 );
+ pHull->m_nRenderMode = kRenderTransTexture;
+ return pHull;
+}
+
+
+LINK_ENTITY_TO_CLASS( xen_spore_small, CXenSporeSmall );
+LINK_ENTITY_TO_CLASS( xen_spore_medium, CXenSporeMed );
+LINK_ENTITY_TO_CLASS( xen_spore_large, CXenSporeLarge );
+LINK_ENTITY_TO_CLASS( xen_hull, CXenHull );
+
+void CXenSporeSmall::Spawn( void )
+{
+ m_nSkin = 0;
+ CXenSpore::Spawn();
+ UTIL_SetSize( this, Vector(-16,-16,0), Vector(16,16,64));
+}
+void CXenSporeMed::Spawn( void )
+{
+ m_nSkin = 1;
+ CXenSpore::Spawn();
+ UTIL_SetSize( this, Vector(-40,-40,0), Vector(40,40,120));
+}
+
+
+// I just eyeballed these -- fill in hulls for the legs
+const Vector CXenSporeLarge::m_hullSizes[] =
+{
+ Vector( 90, -25, 0 ),
+ Vector( 25, 75, 0 ),
+ Vector( -15, -100, 0 ),
+ Vector( -90, -35, 0 ),
+ Vector( -90, 60, 0 ),
+};
+
+void CXenSporeLarge::Spawn( void )
+{
+ m_nSkin = 2;
+ CXenSpore::Spawn();
+ UTIL_SetSize( this, Vector(-48,-48,110), Vector(48,48,240));
+
+ Vector forward, right;
+
+ AngleVectors( GetAbsAngles(), &forward, &right, NULL );
+
+ // Rotate the leg hulls into position
+ for ( int i = 0; i < ARRAYSIZE(m_hullSizes); i++ )
+ {
+ CXenHull::CreateHull( this, Vector(-12, -12, 0 ), Vector( 12, 12, 120 ), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right) );
+ }
+}
+
+void CXenSpore::Spawn( void )
+{
+ Precache();
+
+ SetModel( pModelNames[m_nSkin] );
+ SetMoveType( MOVETYPE_NONE );
+ SetSolid( SOLID_BBOX );
+ m_takedamage = DAMAGE_NO;
+
+// SetActivity( ACT_IDLE );
+ SetSequence( 0 );
+ SetCycle( random->RandomFloat( 0.0f, 1.0f ) );
+ m_flPlaybackRate = random->RandomFloat( 0.7f, 1.4f );
+ ResetSequenceInfo( );
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.4f ) ); // Load balance these a bit
+}
+
+const char *CXenSpore::pModelNames[] =
+{
+ "models/fungus(small).mdl",
+ "models/fungus.mdl",
+ "models/fungus(large).mdl",
+};
+
+
+void CXenSpore::Precache( void )
+{
+ PrecacheModel( (char *)pModelNames[m_nSkin] );
+}
+
+
+void CXenSpore::Touch( CBaseEntity *pOther )
+{
+}
+
+
+
+//=========================================================
+// WaitTillLand - in order to emit their meaty scent from
+// the proper location, gibs should wait until they stop
+// bouncing to emit their scent. That's what this function
+// does.
+//=========================================================
+void CHL1Gib::WaitTillLand ( void )
+{
+ if ( !IsInWorld() )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+
+ if ( GetAbsVelocity() == vec3_origin )
+ {
+ /* SetRenderColorA( 255 );
+ m_nRenderMode = kRenderTransTexture;
+ AddSolidFlags( FSOLID_NOT_SOLID );*/
+
+ SetNextThink( gpGlobals->curtime + m_lifeTime );
+ SetThink ( &CBaseEntity::SUB_FadeOut );
+
+ // If you bleed, you stink!
+ /* if ( m_bloodColor != DONT_BLEED )
+ {
+ // ok, start stinkin!
+ CSoundEnt::InsertSound ( bits_SOUND_MEAT, pev->origin, 384, 25 );
+ }*/
+ }
+ else
+ {
+ // wait and check again in another half second.
+ SetNextThink( gpGlobals->curtime + 0.5 );
+ }
+}
+
+//
+// Gib bounces on the ground or wall, sponges some blood down, too!
+//
+void CHL1Gib::BounceGibTouch ( CBaseEntity *pOther )
+{
+ Vector vecSpot;
+ trace_t tr;
+
+ if ( GetFlags() & FL_ONGROUND)
+ {
+ SetAbsVelocity( GetAbsVelocity() * 0.9 );
+
+ SetAbsAngles( QAngle( 0, GetAbsAngles().y, 0 ) );
+ SetLocalAngularVelocity( QAngle( 0, GetLocalAngularVelocity().y, 0 ) );
+ }
+ else
+ {
+ if ( m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED )
+ {
+ vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );//move up a bit, and trace down.
+ UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+
+ UTIL_BloodDecalTrace( &tr, m_bloodColor );
+
+ m_cBloodDecals--;
+ }
+
+ if ( m_material != matNone && random->RandomInt( 0, 2 ) == 0 )
+ {
+ float volume;
+ float zvel = fabs( GetAbsVelocity().z );
+
+ volume = 0.8 * MIN(1.0, ((float)zvel) / 450.0);
+
+ CBreakable::MaterialSoundRandom( entindex(), (Materials)m_material, volume );
+ }
+ }
+}
+
+//
+// Sticky gib puts blood on the wall and stays put.
+//
+void CHL1Gib::StickyGibTouch ( CBaseEntity *pOther )
+{
+ Vector vecSpot;
+ trace_t tr;
+
+ SetThink ( &CHL1Gib::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 10 );
+
+ if ( !FClassnameIs( pOther, "worldspawn" ) )
+ {
+ SetNextThink( gpGlobals->curtime );
+ return;
+ }
+
+ UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 32, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ UTIL_BloodDecalTrace( &tr, m_bloodColor );
+
+ SetAbsVelocity( tr.plane.normal * -1 );
+
+ QAngle qAngle;
+
+ VectorAngles( GetAbsVelocity(), qAngle );
+ SetAbsAngles( qAngle );
+
+ SetAbsVelocity( vec3_origin );
+ SetLocalAngularVelocity( QAngle( 0, 0, 0 ) );
+ SetMoveType( MOVETYPE_NONE );
+}
+
+//
+// Throw a chunk
+//
+void CHL1Gib::Spawn( const char *szGibModel )
+{
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE );
+
+ SetFriction( 0.55 ); // deading the bounce a bit
+
+ // sometimes an entity inherits the edict from a former piece of glass,
+ // and will spawn using the same render FX or rendermode! bad!
+ SetRenderColorA( 255 );
+ m_nRenderMode = kRenderNormal;
+ m_nRenderFX = kRenderFxNone;
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetClassname( "gib" );
+
+ SetModel( szGibModel );
+ UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0));
+
+ SetNextThink( gpGlobals->curtime + 4 );
+
+ m_lifeTime = 25;
+
+ SetThink ( &CHL1Gib::WaitTillLand );
+ SetTouch ( &CHL1Gib::BounceGibTouch );
+
+ m_material = matNone;
+ m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain).
+}
+
+LINK_ENTITY_TO_CLASS( hl1gib, CHL1Gib );
+
+BEGIN_DATADESC( CHL1Gib )
+ // Function Pointers
+ DEFINE_FUNCTION( BounceGibTouch ),
+ DEFINE_FUNCTION( StickyGibTouch ),
+ DEFINE_FUNCTION( WaitTillLand ),
+
+ DEFINE_FIELD( m_bloodColor, FIELD_INTEGER ),
+ DEFINE_FIELD( m_cBloodDecals, FIELD_INTEGER ),
+ DEFINE_FIELD( m_material, FIELD_INTEGER ),
+ DEFINE_FIELD( m_lifeTime, FIELD_FLOAT ),
+END_DATADESC()
+
+#define SF_ENDSECTION_USEONLY 0x0001
+
+class CTriggerEndSection : public CBaseEntity
+{
+ DECLARE_CLASS( CTriggerEndSection, CBaseEntity );
+
+public:
+ void Spawn( void );
+ void InputEndSection( inputdata_t &data );
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection );
+
+BEGIN_DATADESC( CTriggerEndSection )
+ DEFINE_INPUTFUNC( FIELD_VOID, "EndSection", InputEndSection ),
+END_DATADESC()
+
+void CTriggerEndSection::Spawn( void )
+{
+ if ( gpGlobals->deathmatch )
+ {
+ UTIL_Remove( this );
+ return;
+ }
+}
+
+void CTriggerEndSection::InputEndSection( inputdata_t &data )
+{
+ CBaseEntity *pPlayer = UTIL_GetLocalPlayer();
+
+ if ( pPlayer )
+ {
+ //HACKY MCHACK - This works, but it's nasty. Alfred is going to fix a
+ //bug in gameui that prevents you from dropping to the main menu after
+ // calling disconnect.
+ engine->ClientCommand ( pPlayer->edict(), "toggleconsole;disconnect\n");
+ }
+
+ UTIL_Remove( this );
+}
diff --git a/game/server/hl1/hl1_ents.h b/game/server/hl1/hl1_ents.h
new file mode 100644
index 0000000..a5eb6fe
--- /dev/null
+++ b/game/server/hl1/hl1_ents.h
@@ -0,0 +1,73 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef HL1_ENTS_H
+#define HL1_ENTS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+/**********************
+ Pendulum
+/**********************/
+
+class CPendulum : public CBaseToggle
+{
+ DECLARE_CLASS( CPendulum, CBaseToggle );
+public:
+ void Spawn ( void );
+ void KeyValue( KeyValueData *pkvd );
+ void Swing( void );
+ void PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ void Stop( void );
+ void Touch( CBaseEntity *pOther );
+ void RopeTouch ( CBaseEntity *pOther );// this touch func makes the pendulum a rope
+ void Blocked( CBaseEntity *pOther );
+
+ // Input handlers.
+ void InputActivate( inputdata_t &inputdata );
+
+ DECLARE_DATADESC();
+
+ float m_flAccel; // Acceleration
+ float m_flTime;
+ float m_flDamp;
+ float m_flMaxSpeed;
+ float m_flDampSpeed;
+ QAngle m_vCenter;
+ QAngle m_vStart;
+ float m_flBlockDamage;
+
+ EHANDLE m_hEnemy;
+};
+
+class CHL1Gib : public CBaseEntity
+{
+ DECLARE_CLASS( CHL1Gib, CBaseEntity );
+
+public:
+ void Spawn( const char *szGibModel );
+ void BounceGibTouch ( CBaseEntity *pOther );
+ void StickyGibTouch ( CBaseEntity *pOther );
+ void WaitTillLand( void );
+ void LimitVelocity( void );
+
+ virtual int ObjectCaps( void ) { return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; }
+ static void SpawnHeadGib( CBaseEntity *pVictim );
+ static void SpawnRandomGibs( CBaseEntity *pVictim, int cGibs, int human );
+ static void SpawnStickyGibs( CBaseEntity *pVictim, Vector vecOrigin, int cGibs );
+
+ int m_bloodColor;
+ int m_cBloodDecals;
+ int m_material;
+ float m_lifeTime;
+
+ DECLARE_DATADESC();
+};
+
+
+#endif // HL1_ENTS_H
diff --git a/game/server/hl1/hl1_env_speaker.cpp b/game/server/hl1/hl1_env_speaker.cpp
new file mode 100644
index 0000000..97bf22c
--- /dev/null
+++ b/game/server/hl1/hl1_env_speaker.cpp
@@ -0,0 +1,219 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "player.h"
+#include "mathlib/mathlib.h"
+#include "ai_speech.h"
+#include "stringregistry.h"
+#include "gamerules.h"
+#include "game.h"
+#include <ctype.h>
+#include "entitylist.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "ndebugoverlay.h"
+#include "soundscape.h"
+
+#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements
+
+// ===================================================================================
+//
+// Speaker class. Used for announcements per level, for door lock/unlock spoken voice.
+//
+
+class CSpeaker : public CPointEntity
+{
+ DECLARE_CLASS( CSpeaker, CPointEntity );
+public:
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ void Spawn( void );
+ void Precache( void );
+ void ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ void SpeakerThink( void );
+
+ virtual int ObjectCaps( void ) { return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); }
+
+ int m_preset; // preset number
+ string_t m_iszMessage;
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( speaker, CSpeaker );
+
+BEGIN_DATADESC( CSpeaker )
+ DEFINE_FIELD( m_preset, FIELD_INTEGER ),
+ DEFINE_KEYFIELD( m_iszMessage, FIELD_STRING, "message" ),
+ DEFINE_THINKFUNC( SpeakerThink ),
+ DEFINE_USEFUNC( ToggleUse ),
+END_DATADESC()
+
+//
+// ambient_generic - general-purpose user-defined static sound
+//
+void CSpeaker::Spawn( void )
+{
+ char* szSoundFile = (char*) STRING( m_iszMessage );
+
+ if ( !m_preset && ( m_iszMessage == NULL_STRING || strlen( szSoundFile ) < 1 ) )
+ {
+ Msg( "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ SetThink( &CSpeaker::SUB_Remove );
+ return;
+ }
+ SetSolid( SOLID_NONE );
+ SetMoveType( MOVETYPE_NONE );
+
+
+ SetThink(&CSpeaker::SpeakerThink);
+ SetNextThink( TICK_NEVER_THINK );
+
+ // allow on/off switching via 'use' function.
+ SetUse ( &CSpeaker::ToggleUse );
+
+ Precache( );
+}
+
+#define ANNOUNCE_MINUTES_MIN 0.25
+#define ANNOUNCE_MINUTES_MAX 2.25
+
+void CSpeaker::Precache( void )
+{
+ if ( !FBitSet ( GetSpawnFlags(), SPEAKER_START_SILENT ) )
+ // set first announcement time for random n second
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 5.0, 15.0 ) );
+}
+void CSpeaker::SpeakerThink( void )
+{
+ char* szSoundFile = NULL;
+ float flvolume = m_iHealth * 0.1;
+ int flags = 0;
+ int pitch = 100;
+
+
+ // Wait for the talking characters to finish first.
+ if ( !g_AIFriendliesTalkSemaphore.IsAvailable( this ) || !g_AIFoesTalkSemaphore.IsAvailable( this ) )
+ {
+ float releaseTime = MAX( g_AIFriendliesTalkSemaphore.GetReleaseTime(), g_AIFoesTalkSemaphore.GetReleaseTime() );
+ SetNextThink( gpGlobals->curtime + releaseTime + random->RandomFloat( 5, 10 ) );
+ return;
+ }
+
+ if (m_preset)
+ {
+ // go lookup preset text, assign szSoundFile
+ switch (m_preset)
+ {
+ case 1: szSoundFile = "C1A0_"; break;
+ case 2: szSoundFile = "C1A1_"; break;
+ case 3: szSoundFile = "C1A2_"; break;
+ case 4: szSoundFile = "C1A3_"; break;
+ case 5: szSoundFile = "C1A4_"; break;
+ case 6: szSoundFile = "C2A1_"; break;
+ case 7: szSoundFile = "C2A2_"; break;
+ case 8: szSoundFile = "C2A3_"; break;
+ case 9: szSoundFile = "C2A4_"; break;
+ case 10: szSoundFile = "C2A5_"; break;
+ case 11: szSoundFile = "C3A1_"; break;
+ case 12: szSoundFile = "C3A2_"; break;
+ }
+ } else
+ szSoundFile = (char*) STRING( m_iszMessage );
+
+ if (szSoundFile[0] == '!')
+ {
+ // play single sentence, one shot
+ UTIL_EmitAmbientSound ( GetSoundSourceIndex(), GetAbsOrigin(), szSoundFile,
+ flvolume, SNDLVL_120dB, flags, pitch);
+
+ // shut off and reset
+ SetNextThink( TICK_NEVER_THINK );
+ }
+ else
+ {
+ // make random announcement from sentence group
+
+ if ( SENTENCEG_PlayRndSz( edict(), szSoundFile, flvolume, SNDLVL_120dB, flags, pitch) < 0 )
+ Msg( "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile);
+
+ // set next announcement time for random 5 to 10 minute delay
+ SetNextThink ( gpGlobals->curtime +
+ random->RandomFloat( ANNOUNCE_MINUTES_MIN * 60.0, ANNOUNCE_MINUTES_MAX * 60.0 ) );
+
+ // time delay until it's ok to speak: used so that two NPCs don't talk at once
+ g_AIFriendliesTalkSemaphore.Acquire( 5, this );
+ g_AIFoesTalkSemaphore.Acquire( 5, this );
+ }
+
+ return;
+}
+
+
+//
+// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one.
+//
+void CSpeaker::ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ int fActive = (GetNextThink() > 0.0);
+
+ // fActive is TRUE only if an announcement is pending
+
+ if ( useType != USE_TOGGLE )
+ {
+ // ignore if we're just turning something on that's already on, or
+ // turning something off that's already off.
+ if ( (fActive && useType == USE_ON) || (!fActive && useType == USE_OFF) )
+ return;
+ }
+
+ if ( useType == USE_ON )
+ {
+ // turn on announcements
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ return;
+ }
+
+ if ( useType == USE_OFF )
+ {
+ // turn off announcements
+ SetNextThink( TICK_NEVER_THINK );
+ return;
+
+ }
+
+ // Toggle announcements
+
+
+ if ( fActive )
+ {
+ // turn off announcements
+ SetNextThink( TICK_NEVER_THINK );
+ }
+ else
+ {
+ // turn on announcements
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+}
+
+// KeyValue - load keyvalue pairs into member data
+// NOTE: called BEFORE spawn!
+
+bool CSpeaker::KeyValue( const char *szKeyName, const char *szValue )
+{
+ // preset
+ if (FStrEq(szKeyName, "preset"))
+ {
+ m_preset = atoi(szValue);
+ return true;
+ }
+ else
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
diff --git a/game/server/hl1/hl1_eventlog.cpp b/game/server/hl1/hl1_eventlog.cpp
new file mode 100644
index 0000000..4c33d64
--- /dev/null
+++ b/game/server/hl1/hl1_eventlog.cpp
@@ -0,0 +1,55 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "../EventLog.h"
+
+class CHL1EventLog : public CEventLog
+{
+private:
+ typedef CEventLog BaseClass;
+
+public:
+ virtual ~CHL1EventLog() {};
+
+public:
+ bool PrintEvent( IGameEvent * event ) // override virtual function
+ {
+ if ( BaseClass::PrintEvent( event ) )
+ {
+ return true;
+ }
+
+ if ( Q_strcmp(event->GetName(), "hl1_") == 0 )
+ {
+ return PrintHL1Event( event );
+ }
+
+ return false;
+ }
+
+protected:
+
+ bool PrintHL1Event( IGameEvent * event ) // print Mod specific logs
+ {
+ // const char * name = event->GetName() + Q_strlen("hl1_"); // remove prefix
+
+ return false;
+ }
+
+};
+
+CHL1EventLog g_HL1EventLog;
+
+//-----------------------------------------------------------------------------
+// Singleton access
+//-----------------------------------------------------------------------------
+IGameSystem* GameLogSystem()
+{
+ return &g_HL1EventLog;
+}
+
diff --git a/game/server/hl1/hl1_func_recharge.cpp b/game/server/hl1/hl1_func_recharge.cpp
new file mode 100644
index 0000000..d27b5bf
--- /dev/null
+++ b/game/server/hl1/hl1_func_recharge.cpp
@@ -0,0 +1,239 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+/*
+
+===== h_battery.cpp ========================================================
+
+ battery-related code
+
+*/
+
+#include "cbase.h"
+#include "gamerules.h"
+#include "player.h"
+#include "engine/IEngineSound.h"
+#include "in_buttons.h"
+
+ConVar sk_suitcharger( "sk_suitcharger","0" );
+#define HL1_MAX_ARMOR 100
+
+class CRecharge : public CBaseToggle
+{
+public:
+ DECLARE_CLASS( CRecharge, CBaseToggle );
+
+ void Spawn( );
+
+ virtual void Precache();
+
+ bool CreateVPhysics();
+ void Off(void);
+ void Recharge(void);
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ virtual int ObjectCaps( void ) { return (BaseClass::ObjectCaps() | m_iCaps ); }
+
+ DECLARE_DATADESC();
+
+ float m_flNextCharge;
+ int m_iReactivate ; // DeathMatch Delay until reactvated
+ int m_iJuice;
+ int m_iOn; // 0 = off, 1 = startup, 2 = going
+ float m_flSoundTime;
+
+ int m_iCaps;
+
+ COutputFloat m_OutRemainingCharge;
+};
+
+BEGIN_DATADESC( CRecharge )
+
+ DEFINE_FIELD( m_flNextCharge, FIELD_TIME ),
+ DEFINE_FIELD( m_iReactivate, FIELD_INTEGER),
+ DEFINE_FIELD( m_iJuice, FIELD_INTEGER),
+ DEFINE_FIELD( m_iOn, FIELD_INTEGER),
+ DEFINE_FIELD( m_flSoundTime, FIELD_TIME ),
+ DEFINE_FIELD( m_iCaps, FIELD_INTEGER ),
+
+ // Function Pointers
+ DEFINE_FUNCTION( Off ),
+ DEFINE_FUNCTION( Recharge ),
+
+ DEFINE_OUTPUT(m_OutRemainingCharge, "OutRemainingCharge"),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS(func_recharge, CRecharge);
+
+
+bool CRecharge::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq(szKeyName, "style") ||
+ FStrEq(szKeyName, "height") ||
+ FStrEq(szKeyName, "value1") ||
+ FStrEq(szKeyName, "value2") ||
+ FStrEq(szKeyName, "value3"))
+ {
+ }
+ else if (FStrEq(szKeyName, "dmdelay"))
+ {
+ m_iReactivate = atoi(szValue);
+ }
+ else
+ return BaseClass::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+void CRecharge::Spawn()
+{
+ Precache( );
+
+ SetSolid( SOLID_BSP );
+ SetMoveType( MOVETYPE_PUSH );
+
+ SetModel( STRING( GetModelName() ) );
+ m_iJuice = sk_suitcharger.GetFloat();
+ SetTextureFrameIndex( 0 );
+
+ m_iCaps = FCAP_CONTINUOUS_USE;
+
+ CreateVPhysics();
+}
+
+void CRecharge::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "SuitRecharge.Deny" );
+ PrecacheScriptSound( "SuitRecharge.Start" );
+ PrecacheScriptSound( "SuitRecharge.ChargingLoop" );
+}
+
+bool CRecharge::CreateVPhysics()
+{
+ VPhysicsInitStatic();
+ return true;
+}
+
+void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Make sure that we have a caller
+ if (!pActivator)
+ return;
+
+ // if it's not a player, ignore
+ if ( !pActivator->IsPlayer() )
+ return;
+
+ CBasePlayer *pPlayer = dynamic_cast<CBasePlayer *>( pActivator );
+
+ if ( pPlayer == NULL )
+ return;
+
+ // Reset to a state of continuous use.
+ m_iCaps = FCAP_CONTINUOUS_USE;
+
+ // if there is no juice left, turn it off
+ if (m_iJuice <= 0)
+ {
+ SetTextureFrameIndex( 1 );
+ Off();
+ }
+
+ // if the player doesn't have the suit, or there is no juice left, make the deny noise
+ if ( m_iJuice <= 0 )
+ {
+ if (m_flSoundTime <= gpGlobals->curtime)
+ {
+ m_flSoundTime = gpGlobals->curtime + 0.62;
+ EmitSound( "SuitRecharge.Deny" );
+ }
+ return;
+ }
+
+ // If we're over our limit, debounce our keys
+ if ( pPlayer->ArmorValue() >= HL1_MAX_ARMOR)
+ {
+ // Make the user re-use me to get started drawing health.
+ pPlayer->m_afButtonPressed &= ~IN_USE;
+ m_iCaps = FCAP_IMPULSE_USE;
+
+ EmitSound( "SuitRecharge.Deny" );
+ return;
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.25 );
+ SetThink(&CRecharge::Off);
+
+ // Time to recharge yet?
+
+ if (m_flNextCharge >= gpGlobals->curtime)
+ return;
+
+ m_hActivator = pActivator;
+
+
+ // Play the on sound or the looping charging sound
+ if (!m_iOn)
+ {
+ m_iOn++;
+ EmitSound( "SuitRecharge.Start" );
+ m_flSoundTime = 0.56 + gpGlobals->curtime;
+ }
+ if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->curtime))
+ {
+ m_iOn++;
+ CPASAttenuationFilter filter( this, "SuitRecharge.ChargingLoop" );
+ filter.MakeReliable();
+ EmitSound( filter, entindex(), "SuitRecharge.ChargingLoop" );
+ }
+
+ CBasePlayer *pl = (CBasePlayer *) m_hActivator.Get();
+
+ // charge the player
+ if (pl->ArmorValue() < HL1_MAX_ARMOR)
+ {
+ m_iJuice--;
+ pl->IncrementArmorValue( 1, HL1_MAX_ARMOR );
+ }
+
+ // Send the output.
+ float flRemaining = m_iJuice / sk_suitcharger.GetFloat();
+ m_OutRemainingCharge.Set(flRemaining, pActivator, this);
+
+ // govern the rate of charge
+ m_flNextCharge = gpGlobals->curtime + 0.1;
+}
+
+void CRecharge::Recharge(void)
+{
+ m_iJuice = sk_suitcharger.GetFloat();
+ SetTextureFrameIndex( 0 );
+ SetThink( &CBaseEntity::SUB_DoNothing );
+}
+
+void CRecharge::Off(void)
+{
+ // Stop looping sound.
+ if (m_iOn > 1)
+ {
+ StopSound( "SuitRecharge.ChargingLoop" );
+ }
+
+ m_iOn = 0;
+
+ if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime() ) > 0) )
+ {
+ SetNextThink( gpGlobals->curtime + m_iReactivate );
+ SetThink(&CRecharge::Recharge);
+ }
+ else
+ SetThink( NULL );
+} \ No newline at end of file
diff --git a/game/server/hl1/hl1_func_tank.cpp b/game/server/hl1/hl1_func_tank.cpp
new file mode 100644
index 0000000..8f89819
--- /dev/null
+++ b/game/server/hl1/hl1_func_tank.cpp
@@ -0,0 +1,1700 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "Sprite.h"
+#include "EnvLaser.h"
+#include "basecombatweapon.h"
+#include "explode.h"
+#include "eventqueue.h"
+#include "gamerules.h"
+#include "ammodef.h"
+#include "in_buttons.h"
+#include "soundent.h"
+#include "ndebugoverlay.h"
+#include "grenade_beam.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "triggers.h"
+#include "physics_cannister.h"
+
+#include "player.h"
+#include "entitylist.h"
+
+
+#define SF_TANK_ACTIVE 0x0001
+#define SF_TANK_PLAYER 0x0002
+#define SF_TANK_HUMANS 0x0004
+#define SF_TANK_ALIENS 0x0008
+#define SF_TANK_LINEOFSIGHT 0x0010
+#define SF_TANK_CANCONTROL 0x0020
+#define SF_TANK_DAMAGE_KICK 0x0040 // Kick when take damage
+#define SF_TANK_AIM_AT_POS 0x0080 // Aim at a particular position
+#define SF_TANK_SOUNDON 0x8000
+
+
+enum TANKBULLET
+{
+ TANK_BULLET_NONE = 0,
+ TANK_BULLET_SMALL = 1,
+ TANK_BULLET_MEDIUM = 2,
+ TANK_BULLET_LARGE = 3,
+};
+
+#define FUNCTANK_FIREVOLUME 1000
+
+
+// Custom damage
+// env_laser (duration is 0.5 rate of fire)
+// rockets
+// explosion?
+
+class CFuncTank : public CBaseEntity
+{
+ DECLARE_CLASS( CFuncTank, CBaseEntity );
+public:
+ ~CFuncTank( void );
+ void Spawn( void );
+ void Activate( void );
+ void Precache( void );
+ bool CreateVPhysics( void );
+ bool KeyValue( const char *szKeyName, const char *szValue );
+
+ int ObjectCaps( void );
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ void Think( void );
+ void TrackTarget( void );
+ int DrawDebugTextOverlays(void);
+
+ virtual void FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
+ virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
+
+ void StartRotSound( void );
+ void StopRotSound( void );
+
+ inline bool IsActive( void ) { return (m_spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; }
+
+ // Input handlers.
+ void InputActivate( inputdata_t &inputdata );
+ void InputDeactivate( inputdata_t &inputdata );
+ void InputSetFireRate( inputdata_t &inputdata );
+ void InputSetTargetPosition( inputdata_t &inputdata );
+ void InputSetTargetEntityName( inputdata_t &inputdata );
+ void InputSetTargetEntity( inputdata_t &inputdata );
+
+ void TankActivate(void);
+ void TankDeactivate(void);
+
+ inline bool CanFire( void );
+ bool InRange( float range );
+
+ void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, trace_t &tr );
+ void TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType);
+
+ Vector WorldBarrelPosition( void )
+ {
+ EntityMatrix tmp;
+ tmp.InitFromEntity( this );
+ return tmp.LocalToWorld( m_barrelPos );
+ }
+
+ void UpdateMatrix( void )
+ {
+ m_parentMatrix.InitFromEntity( GetParent() ? GetParent() : NULL );
+ }
+ QAngle AimBarrelAt( const Vector &parentTarget );
+
+ bool ShouldSavePhysics() { return false; }
+
+ DECLARE_DATADESC();
+
+ bool OnControls( CBaseEntity *pTest );
+ bool StartControl( CBasePlayer *pController );
+ void StopControl( void );
+ void ControllerPostFrame( void );
+
+ CBaseEntity *FindTarget( string_t targetName, CBaseEntity *pActivator );
+
+protected:
+ CBasePlayer* m_pController;
+ float m_flNextAttack;
+ Vector m_vecControllerUsePos;
+
+ float m_yawCenter; // "Center" yaw
+ float m_yawRate; // Max turn rate to track targets
+ float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center)
+ // Zero is full rotation
+ float m_yawTolerance; // Tolerance angle
+
+ float m_pitchCenter; // "Center" pitch
+ float m_pitchRate; // Max turn rate on pitch
+ float m_pitchRange; // Range of pitch motion as above
+ float m_pitchTolerance; // Tolerance angle
+
+ float m_fireLast; // Last time I fired
+ float m_fireRate; // How many rounds/second
+ float m_lastSightTime;// Last time I saw target
+ float m_persist; // Persistence of firing (how long do I shoot when I can't see)
+ float m_persist2; // Secondary persistence of firing (randomly shooting when I can't see)
+ float m_persist2burst;// How long secondary persistence burst lasts
+ float m_minRange; // Minimum range to aim/track
+ float m_maxRange; // Max range to aim/track
+
+ Vector m_barrelPos; // Length of the freakin barrel
+ float m_spriteScale; // Scale of any sprites we shoot
+ string_t m_iszSpriteSmoke;
+ string_t m_iszSpriteFlash;
+ TANKBULLET m_bulletType; // Bullet type
+ int m_iBulletDamage; // 0 means use Bullet type's default damage
+
+ Vector m_sightOrigin; // Last sight of target
+ int m_spread; // firing spread
+ string_t m_iszMaster; // Master entity (game_team_master or multisource)
+
+ int m_iSmallAmmoType;
+ int m_iMediumAmmoType;
+ int m_iLargeAmmoType;
+
+ string_t m_soundStartRotate;
+ string_t m_soundStopRotate;
+ string_t m_soundLoopRotate;
+
+ string_t m_targetEntityName;
+ EHANDLE m_hTarget;
+ Vector m_vTargetPosition;
+ EntityMatrix m_parentMatrix;
+
+ COutputEvent m_OnFire;
+ COutputEvent m_OnLoseTarget;
+ COutputEvent m_OnAquireTarget;
+
+ CHandle<CBaseTrigger> m_hControlVolume;
+ string_t m_iszControlVolume;
+};
+
+
+BEGIN_DATADESC( CFuncTank )
+
+ DEFINE_KEYFIELD( m_yawRate, FIELD_FLOAT, "yawrate" ),
+ DEFINE_KEYFIELD( m_yawRange, FIELD_FLOAT, "yawrange" ),
+ DEFINE_KEYFIELD( m_yawTolerance, FIELD_FLOAT, "yawtolerance" ),
+ DEFINE_KEYFIELD( m_pitchRate, FIELD_FLOAT, "pitchrate" ),
+ DEFINE_KEYFIELD( m_pitchRange, FIELD_FLOAT, "pitchrange" ),
+ DEFINE_KEYFIELD( m_pitchTolerance, FIELD_FLOAT, "pitchtolerance" ),
+ DEFINE_KEYFIELD( m_fireRate, FIELD_FLOAT, "firerate" ),
+ DEFINE_KEYFIELD( m_persist, FIELD_FLOAT, "persistence" ),
+ DEFINE_KEYFIELD( m_persist2, FIELD_FLOAT, "persistence2" ),
+ DEFINE_KEYFIELD( m_minRange, FIELD_FLOAT, "minRange" ),
+ DEFINE_KEYFIELD( m_maxRange, FIELD_FLOAT, "maxRange" ),
+ DEFINE_KEYFIELD( m_spriteScale, FIELD_FLOAT, "spritescale" ),
+ DEFINE_KEYFIELD( m_iszSpriteSmoke, FIELD_STRING, "spritesmoke" ),
+ DEFINE_KEYFIELD( m_iszSpriteFlash, FIELD_STRING, "spriteflash" ),
+ DEFINE_KEYFIELD( m_bulletType, FIELD_INTEGER, "bullet" ),
+ DEFINE_KEYFIELD( m_spread, FIELD_INTEGER, "firespread" ),
+ DEFINE_KEYFIELD( m_iBulletDamage, FIELD_INTEGER, "bullet_damage" ),
+ DEFINE_KEYFIELD( m_iszMaster, FIELD_STRING, "master" ),
+ DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ),
+ DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ),
+ DEFINE_KEYFIELD( m_soundLoopRotate, FIELD_SOUNDNAME, "rotatesound" ),
+ DEFINE_KEYFIELD( m_iszControlVolume, FIELD_STRING, "control_volume" ),
+
+ DEFINE_FIELD( m_yawCenter, FIELD_FLOAT ),
+ DEFINE_FIELD( m_pitchCenter, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fireLast, FIELD_TIME ),
+ DEFINE_FIELD( m_lastSightTime, FIELD_TIME ),
+ DEFINE_FIELD( m_barrelPos, FIELD_VECTOR ),
+ DEFINE_FIELD( m_sightOrigin, FIELD_VECTOR ),
+ DEFINE_FIELD( m_pController, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_vecControllerUsePos, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flNextAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_targetEntityName, FIELD_STRING ),
+ DEFINE_FIELD( m_vTargetPosition, FIELD_VECTOR ),
+ DEFINE_FIELD( m_persist2burst, FIELD_FLOAT),
+ //DEFINE_FIELD( m_parentMatrix, FIELD_MATRIX ), // DON'T SAVE
+ DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hControlVolume, FIELD_EHANDLE ),
+
+
+ // Do not save these.
+ //DEFINE_FIELD( m_iSmallAmmoType, FIELD_INTEGER ),
+ //DEFINE_FIELD( m_iMediumAmmoType, FIELD_INTEGER ),
+ //DEFINE_FIELD( m_iLargeAmmoType, FIELD_INTEGER ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFireRate", InputSetFireRate ),
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetPosition", InputSetTargetPosition ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetEntityName", InputSetTargetEntityName ),
+ DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetTargetEntity", InputSetTargetEntity ),
+
+ // Outputs
+ DEFINE_OUTPUT(m_OnFire, "OnFire"),
+ DEFINE_OUTPUT(m_OnLoseTarget, "OnLoseTarget"),
+ DEFINE_OUTPUT(m_OnAquireTarget, "OnAquireTarget"),
+
+END_DATADESC()
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFuncTank::~CFuncTank( void )
+{
+ if ( m_soundLoopRotate != NULL_STRING && (m_spawnflags & SF_TANK_SOUNDON) )
+ {
+ StopSound( entindex(), CHAN_STATIC, STRING(m_soundLoopRotate) );
+ }
+}
+
+
+int CFuncTank::ObjectCaps( void )
+{
+ int iCaps = BaseClass::ObjectCaps();
+
+ if ( m_spawnflags & SF_TANK_CANCONTROL )
+ {
+ iCaps |= FCAP_IMPULSE_USE;
+ }
+
+ return iCaps;
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose:
+//------------------------------------------------------------------------------
+inline bool CFuncTank::CanFire( void )
+{
+ float flTimeDelay = gpGlobals->curtime - m_lastSightTime;
+ // Fire when can't see enemy if time is less that persistence time
+ if ( flTimeDelay <= m_persist)
+ {
+ return true;
+ }
+ // Fire when I'm in a persistence2 burst
+ else if (flTimeDelay <= m_persist2burst)
+ {
+ return true;
+ }
+ // If less than persistence2, occasionally do another burst
+ else if (flTimeDelay <= m_persist2)
+ {
+ if (random->RandomInt(0,30)==0)
+ {
+ m_persist2burst = flTimeDelay+0.5;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose: Input handler for activating the tank.
+//------------------------------------------------------------------------------
+void CFuncTank::InputActivate( inputdata_t &inputdata )
+{
+ TankActivate();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::TankActivate(void)
+{
+ m_spawnflags |= SF_TANK_ACTIVE;
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ m_fireLast = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for deactivating the tank.
+//-----------------------------------------------------------------------------
+void CFuncTank::InputDeactivate( inputdata_t &inputdata )
+{
+ TankDeactivate();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::TankDeactivate(void)
+{
+ m_spawnflags &= ~SF_TANK_ACTIVE;
+ m_fireLast = 0;
+ StopRotSound();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for changing the name of the tank's target entity.
+//-----------------------------------------------------------------------------
+void CFuncTank::InputSetTargetEntityName( inputdata_t &inputdata )
+{
+ m_targetEntityName = inputdata.value.StringID();
+ m_hTarget = FindTarget( m_targetEntityName, inputdata.pActivator );
+
+ // No longer aim at target position if have one
+ m_spawnflags &= ~SF_TANK_AIM_AT_POS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for setting a new target entity by ehandle.
+//-----------------------------------------------------------------------------
+void CFuncTank::InputSetTargetEntity( inputdata_t &inputdata )
+{
+ if (inputdata.value.Entity() != NULL)
+ {
+ m_targetEntityName = inputdata.value.Entity()->GetEntityName();
+ }
+ else
+ {
+ m_targetEntityName = NULL_STRING;
+ }
+ m_hTarget = inputdata.value.Entity();
+
+ // No longer aim at target position if have one
+ m_spawnflags &= ~SF_TANK_AIM_AT_POS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for setting the rate of fire in shots per second.
+//-----------------------------------------------------------------------------
+void CFuncTank::InputSetFireRate( inputdata_t &inputdata )
+{
+ m_fireRate = inputdata.value.Float();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for setting the target as a position.
+//-----------------------------------------------------------------------------
+void CFuncTank::InputSetTargetPosition( inputdata_t &inputdata )
+{
+ m_spawnflags |= SF_TANK_AIM_AT_POS;
+ m_hTarget = NULL;
+
+ inputdata.value.Vector3D(m_vTargetPosition);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw any debug text overlays
+// Output : Current text offset from the top
+//-----------------------------------------------------------------------------
+int CFuncTank::DrawDebugTextOverlays(void)
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ // --------------
+ // State
+ // --------------
+ char tempstr[255];
+ if (IsActive())
+ {
+ Q_strncpy(tempstr,"State: Active",sizeof(tempstr));
+ }
+ else
+ {
+ Q_strncpy(tempstr,"State: Inactive",sizeof(tempstr));
+ }
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+
+ // -------------------
+ // Print Firing Speed
+ // --------------------
+ Q_snprintf(tempstr,sizeof(tempstr),"Fire Rate: %f",m_fireRate);
+
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+
+ // --------------
+ // Print Target
+ // --------------
+ if (m_hTarget!=NULL)
+ {
+ Q_snprintf(tempstr,sizeof(tempstr),"Target: %s",m_hTarget->GetDebugName());
+ }
+ else
+ {
+ Q_snprintf(tempstr,sizeof(tempstr),"Target: - ");
+ }
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+
+ // --------------
+ // Target Pos
+ // --------------
+ if (m_spawnflags & SF_TANK_AIM_AT_POS)
+ {
+ Q_snprintf(tempstr,sizeof(tempstr),"Aim Pos: %3.0f %3.0f %3.0f",m_vTargetPosition.x,m_vTargetPosition.y,m_vTargetPosition.z);
+ }
+ else
+ {
+ Q_snprintf(tempstr,sizeof(tempstr),"Aim Pos: - ");
+ }
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+
+ }
+ return text_offset;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pAttacker -
+// flDamage -
+// vecDir -
+// ptr -
+// bitsDamageType -
+//-----------------------------------------------------------------------------
+void CFuncTank::TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType)
+{
+ if (m_spawnflags & SF_TANK_DAMAGE_KICK)
+ {
+ // Deflect the func_tank
+ // Only adjust yaw for now
+ if (pAttacker)
+ {
+ Vector vFromAttacker = (pAttacker->EyePosition()-GetAbsOrigin());
+ vFromAttacker.z = 0;
+ VectorNormalize(vFromAttacker);
+
+ Vector vFromAttacker2 = (ptr->endpos-GetAbsOrigin());
+ vFromAttacker2.z = 0;
+ VectorNormalize(vFromAttacker2);
+
+
+ Vector vCrossProduct;
+ CrossProduct(vFromAttacker,vFromAttacker2, vCrossProduct);
+ Msg( "%f\n",vCrossProduct.z);
+ QAngle angles;
+ angles = GetLocalAngles();
+ if (vCrossProduct.z > 0)
+ {
+ angles.y += 10;
+ }
+ else
+ {
+ angles.y -= 10;
+ }
+
+ // Limit against range in y
+ if ( angles.y > m_yawCenter + m_yawRange )
+ {
+ angles.y = m_yawCenter + m_yawRange;
+ }
+ else if ( angles.y < (m_yawCenter - m_yawRange) )
+ {
+ angles.y = (m_yawCenter - m_yawRange);
+ }
+
+ SetLocalAngles( angles );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : targetName -
+// pActivator -
+//-----------------------------------------------------------------------------
+CBaseEntity *CFuncTank::FindTarget( string_t targetName, CBaseEntity *pActivator )
+{
+ return gEntList.FindEntityGenericNearest( STRING( targetName ), GetAbsOrigin(), 0, this, pActivator );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Caches entity key values until spawn is called.
+// Input : szKeyName -
+// szValue -
+// Output :
+//-----------------------------------------------------------------------------
+bool CFuncTank::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if (FStrEq(szKeyName, "barrel"))
+ {
+ m_barrelPos.x = atof(szValue);
+ }
+ else if (FStrEq(szKeyName, "barrely"))
+ {
+ m_barrelPos.y = atof(szValue);
+ }
+ else if (FStrEq(szKeyName, "barrelz"))
+ {
+ m_barrelPos.z = atof(szValue);
+ }
+ else
+ return BaseClass::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+
+static Vector gTankSpread[] =
+{
+ Vector( 0, 0, 0 ), // perfect
+ Vector( 0.025, 0.025, 0.025 ), // small cone
+ Vector( 0.05, 0.05, 0.05 ), // medium cone
+ Vector( 0.1, 0.1, 0.1 ), // large cone
+ Vector( 0.25, 0.25, 0.25 ), // extra-large cone
+};
+#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread)
+
+
+void CFuncTank::Spawn( void )
+{
+ Precache();
+
+ SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything
+ SetSolid( SOLID_VPHYSICS );
+ SetModel( STRING( GetModelName() ) );
+
+ m_yawCenter = GetLocalAngles().y;
+ m_pitchCenter = GetLocalAngles().x;
+ m_vTargetPosition = vec3_origin;
+
+ if ( IsActive() )
+ SetNextThink( gpGlobals->curtime + 1.0f );
+
+ UpdateMatrix();
+
+ m_sightOrigin = WorldBarrelPosition(); // Point at the end of the barrel
+
+ if ( m_spread > MAX_FIRING_SPREADS )
+ {
+ m_spread = 0;
+ }
+
+ // No longer aim at target position if have one
+ m_spawnflags &= ~SF_TANK_AIM_AT_POS;
+
+ if (m_spawnflags & SF_TANK_DAMAGE_KICK)
+ {
+ m_takedamage = DAMAGE_YES;
+ }
+
+ // UNDONE: Do this?
+ //m_targetEntityName = m_target;
+ CreateVPhysics();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::Activate( void )
+{
+ BaseClass::Activate();
+
+ m_hControlVolume = NULL;
+
+ // Find our control volume
+ if ( m_iszControlVolume != NULL_STRING )
+ {
+ m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) );
+ }
+
+ if (( !m_hControlVolume ) && (m_spawnflags & SF_TANK_CANCONTROL))
+ {
+ Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) );
+ UTIL_Remove( this );
+ }
+}
+
+bool CFuncTank::CreateVPhysics()
+{
+ VPhysicsInitShadow( false, false );
+ return true;
+}
+
+
+void CFuncTank::Precache( void )
+{
+ m_iSmallAmmoType = GetAmmoDef()->Index("9mmRound");
+ m_iMediumAmmoType = GetAmmoDef()->Index("9mmRound");
+ m_iLargeAmmoType = GetAmmoDef()->Index("12mmRound");
+
+ if ( m_iszSpriteSmoke != NULL_STRING )
+ PrecacheModel( STRING(m_iszSpriteSmoke) );
+ if ( m_iszSpriteFlash != NULL_STRING )
+ PrecacheModel( STRING(m_iszSpriteFlash) );
+
+ if ( m_soundStartRotate != NULL_STRING )
+ PrecacheScriptSound( STRING(m_soundStartRotate) );
+ if ( m_soundStopRotate != NULL_STRING )
+ PrecacheScriptSound( STRING(m_soundStopRotate) );
+ if ( m_soundLoopRotate != NULL_STRING )
+ PrecacheScriptSound( STRING(m_soundLoopRotate) );
+}
+
+
+
+//==================================================================================
+// TANK CONTROLLING
+bool CFuncTank::OnControls( CBaseEntity *pTest )
+{
+ if ( !(m_spawnflags & SF_TANK_CANCONTROL) )
+ return FALSE;
+
+ Vector offset = pTest->GetLocalOrigin() - GetLocalOrigin();
+
+ if ( (m_vecControllerUsePos - pTest->GetLocalOrigin()).Length() < 30 )
+ return TRUE;
+
+ return FALSE;
+}
+
+bool CFuncTank::StartControl( CBasePlayer *pController )
+{
+ if ( m_pController != NULL )
+ return FALSE;
+
+ // Team only or disabled?
+ if ( m_iszMaster != NULL_STRING )
+ {
+ if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) )
+ return FALSE;
+ }
+
+ // Holster player's weapon
+ m_pController = pController;
+ if ( m_pController->GetActiveWeapon() )
+ {
+ m_pController->GetActiveWeapon()->Holster();
+ }
+
+ m_pController->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION;
+ m_vecControllerUsePos = m_pController->GetLocalOrigin();
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ return TRUE;
+}
+
+void CFuncTank::StopControl()
+{
+ // TODO: bring back the controllers current weapon
+ if ( !m_pController )
+ return;
+
+ if ( m_pController->GetActiveWeapon() )
+ m_pController->GetActiveWeapon()->Deploy();
+
+ m_pController->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION;
+ SetNextThink( TICK_NEVER_THINK );
+ m_pController = NULL;
+
+ if ( IsActive() )
+ SetNextThink( gpGlobals->curtime + 1.0f );
+}
+
+// Called each frame by the player's ItemPostFrame
+void CFuncTank::ControllerPostFrame( void )
+{
+ Assert(m_pController != NULL);
+
+ if ( gpGlobals->curtime < m_flNextAttack )
+ return;
+
+ Vector forward;
+ AngleVectors( GetAbsAngles(), &forward );
+ if ( m_pController->m_nButtons & IN_ATTACK )
+ {
+ m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets
+
+ int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate;
+
+ Fire( bulletCount, WorldBarrelPosition(), forward, m_pController );
+
+ // HACKHACK -- make some noise (that the AI can hear)
+ CSoundEnt::InsertSound( SOUND_COMBAT, WorldSpaceCenter(), FUNCTANK_FIREVOLUME, 0.2 );
+
+ m_flNextAttack = gpGlobals->curtime + (1/m_fireRate);
+ }
+}
+
+
+void CFuncTank::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( m_spawnflags & SF_TANK_CANCONTROL )
+ {
+ // player controlled turret
+ CBasePlayer *pPlayer = ToBasePlayer( pActivator );
+ if ( !pPlayer )
+ return;
+
+ if ( value == 2 && useType == USE_SET )
+ {
+ ControllerPostFrame();
+ }
+ else if ( !m_pController && useType != USE_OFF )
+ {
+ // The player must be within the func_tank controls
+ Assert( m_hControlVolume );
+ if ( !m_hControlVolume->IsTouching( pPlayer ) )
+ return;
+
+ pPlayer->SetUseEntity( this );
+ StartControl( pPlayer );
+ }
+ else
+ {
+ StopControl();
+ }
+ }
+ else
+ {
+ if ( !ShouldToggle( useType, IsActive() ) )
+ return;
+
+ if ( IsActive() )
+ {
+ TankDeactivate();
+ }
+ else
+ {
+ TankActivate();
+ }
+ }
+}
+
+
+bool CFuncTank::InRange( float range )
+{
+ if ( range < m_minRange )
+ return FALSE;
+ if ( m_maxRange > 0 && range > m_maxRange )
+ return FALSE;
+
+ return TRUE;
+}
+
+
+void CFuncTank::Think( void )
+{
+ // refresh the matrix
+ UpdateMatrix();
+
+ SetLocalAngularVelocity( vec3_angle );
+ TrackTarget();
+
+ if ( fabs(GetLocalAngularVelocity().x) > 1 || fabs(GetLocalAngularVelocity().y) > 1 )
+ StartRotSound();
+ else
+ StopRotSound();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Aim the offset barrel at a position in parent space
+// Input : parentTarget - the position of the target in parent space
+// Output : Vector - angles in local space
+//-----------------------------------------------------------------------------
+QAngle CFuncTank::AimBarrelAt( const Vector &parentTarget )
+{
+ Vector target = parentTarget - GetLocalOrigin();
+ float quadTarget = target.LengthSqr();
+ float quadTargetXY = target.x*target.x + target.y*target.y;
+
+ // Target is too close! Can't aim at it
+ if ( quadTarget <= m_barrelPos.LengthSqr() )
+ {
+ return GetLocalAngles();
+ }
+ else
+ {
+ // We're trying to aim the offset barrel at an arbitrary point.
+ // To calculate this, I think of the target as being on a sphere with
+ // it's center at the origin of the gun.
+ // The rotation we need is the opposite of the rotation that moves the target
+ // along the surface of that sphere to intersect with the gun's shooting direction
+ // To calculate that rotation, we simply calculate the intersection of the ray
+ // coming out of the barrel with the target sphere (that's the new target position)
+ // and use atan2() to get angles
+
+ // angles from target pos to center
+ float targetToCenterYaw = atan2( target.y, target.x );
+ float centerToGunYaw = atan2( m_barrelPos.y, sqrt( quadTarget - (m_barrelPos.y*m_barrelPos.y) ) );
+
+ float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) );
+ float centerToGunPitch = atan2( -m_barrelPos.z, sqrt( quadTarget - (m_barrelPos.z*m_barrelPos.z) ) );
+ return QAngle( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw - centerToGunYaw ), 0 );
+ }
+}
+
+
+void CFuncTank::TrackTarget( void )
+{
+ trace_t tr;
+ bool updateTime = FALSE, lineOfSight;
+ QAngle angles;
+ Vector barrelEnd;
+ CBaseEntity *pTarget = NULL;
+
+ barrelEnd.Init();
+
+ // Get a position to aim for
+ if (m_pController)
+ {
+ // Tanks attempt to mirror the player's angles
+ angles = m_pController->EyeAngles();
+ SetNextThink( gpGlobals->curtime + 0.05 );
+ }
+ else
+ {
+ if ( IsActive() )
+ {
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+ else
+ {
+ return;
+ }
+
+ // -----------------------------------
+ // Get world target position
+ // -----------------------------------
+ barrelEnd = WorldBarrelPosition();
+ Vector worldTargetPosition;
+ if (m_spawnflags & SF_TANK_AIM_AT_POS)
+ {
+ worldTargetPosition = m_vTargetPosition;
+ }
+ else
+ {
+ CBaseEntity *pEntity = (CBaseEntity *)m_hTarget;
+ if ( !pEntity || ( pEntity->GetFlags() & FL_NOTARGET ) )
+ {
+ if ( m_targetEntityName != NULL_STRING ) // New HL2 behavior
+ {
+ m_hTarget = FindTarget( m_targetEntityName, NULL );
+ }
+ else // HL1 style
+ {
+ m_hTarget = ToBasePlayer( GetContainingEntity( UTIL_FindClientInPVS( edict() ) ) );
+ }
+
+ if ( m_hTarget != NULL )
+ {
+ SetNextThink( gpGlobals->curtime ); // Think again immediately
+ }
+ else
+ {
+ if ( IsActive() )
+ {
+ SetNextThink( gpGlobals->curtime + 2 ); // Wait 2 secs
+ }
+
+ if ( m_fireLast !=0 )
+ {
+ m_OnLoseTarget.FireOutput(this, this);
+ m_fireLast = 0;
+ }
+ }
+
+ return;
+ }
+ pTarget = pEntity;
+
+ // Calculate angle needed to aim at target
+ worldTargetPosition = pEntity->EyePosition();
+ }
+
+ float range = (worldTargetPosition - barrelEnd).Length();
+
+ if ( !InRange( range ) )
+ {
+ m_fireLast = 0;
+ return;
+ }
+
+ UTIL_TraceLine( barrelEnd, worldTargetPosition, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if (m_spawnflags & SF_TANK_AIM_AT_POS)
+ {
+ updateTime = TRUE;
+ m_sightOrigin = m_vTargetPosition;
+ }
+ else
+ {
+ lineOfSight = FALSE;
+ // No line of sight, don't track
+ if ( tr.fraction == 1.0 || tr.m_pEnt == pTarget )
+ {
+ lineOfSight = TRUE;
+
+ CBaseEntity *pInstance = pTarget;
+ if ( InRange( range ) && pInstance && pInstance->IsAlive() )
+ {
+ updateTime = TRUE;
+
+ // Sight position is BodyTarget with no noise (so gun doesn't bob up and down)
+ m_sightOrigin = pInstance->BodyTarget( GetLocalOrigin(), false );
+ }
+ }
+ }
+
+ // Convert targetPosition to parent
+ angles = AimBarrelAt( m_parentMatrix.WorldToLocal( m_sightOrigin ) );
+ }
+
+ // Force the angles to be relative to the center position
+ float offsetY = UTIL_AngleDistance( angles.y, m_yawCenter );
+ float offsetX = UTIL_AngleDistance( angles.x, m_pitchCenter );
+ angles.y = m_yawCenter + offsetY;
+ angles.x = m_pitchCenter + offsetX;
+
+ // Limit against range in y
+
+ // MDB - don't check pitch! If two func_tanks are meant to align,
+ // and one can pitch and the other cannot, this can lead to them getting
+ // different values for angles.y. Nothing is lost by not updating yaw
+ // because the target is not in pitch range.
+
+ bool bOutsideYawRange = ( fabs( offsetY ) > m_yawRange + m_yawTolerance );
+ bool bOutsidePitchRange = ( fabs( offsetX ) > m_pitchRange + m_pitchTolerance );
+
+ Vector vecToTarget = m_sightOrigin - GetLocalOrigin();
+
+ // if target is outside yaw range
+ if ( bOutsideYawRange )
+ {
+ if ( angles.y > m_yawCenter + m_yawRange )
+ {
+ angles.y = m_yawCenter + m_yawRange;
+ }
+ else if ( angles.y < (m_yawCenter - m_yawRange) )
+ {
+ angles.y = (m_yawCenter - m_yawRange);
+ }
+ }
+
+ if ( bOutsidePitchRange || bOutsideYawRange || ( vecToTarget.Length() < ( barrelEnd - GetAbsOrigin() ).Length() ) )
+ {
+ // Don't update if you saw the player, but out of range
+ updateTime = false;
+ }
+
+ if ( updateTime )
+ {
+ m_lastSightTime = gpGlobals->curtime;
+ m_persist2burst = 0;
+ }
+
+ // Move toward target at rate or less
+ float distY = UTIL_AngleDistance( angles.y, GetLocalAngles().y );
+
+ QAngle vecAngVel = GetLocalAngularVelocity();
+ vecAngVel.y = distY * 10;
+ vecAngVel.y = clamp( vecAngVel.y, -m_yawRate, m_yawRate );
+
+ // Limit against range in x
+ angles.x = clamp( angles.x, m_pitchCenter - m_pitchRange, m_pitchCenter + m_pitchRange );
+
+ // Move toward target at rate or less
+ float distX = UTIL_AngleDistance( angles.x, GetLocalAngles().x );
+ vecAngVel.x = distX * 10;
+ vecAngVel.x = clamp( vecAngVel.x, -m_pitchRate, m_pitchRate );
+ SetLocalAngularVelocity( vecAngVel );
+
+ SetMoveDoneTime( 0.1 );
+ if ( m_pController )
+ return;
+
+ if ( CanFire() && ( (fabs(distX) < m_pitchTolerance && fabs(distY) < m_yawTolerance) || (m_spawnflags & SF_TANK_LINEOFSIGHT) ) )
+ {
+ bool fire = FALSE;
+ Vector forward;
+ AngleVectors( GetLocalAngles(), &forward );
+ forward = m_parentMatrix.ApplyRotation( forward );
+
+
+ if ( m_spawnflags & SF_TANK_LINEOFSIGHT )
+ {
+ float length = (m_maxRange > 0) ? m_maxRange : MAX_TRACE_LENGTH;
+ UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.m_pEnt == pTarget )
+ fire = TRUE;
+ }
+ else
+ fire = TRUE;
+
+ if ( fire )
+ {
+ if (m_fireLast == 0)
+ {
+ m_OnAquireTarget.FireOutput(this, this);
+ }
+ FiringSequence( barrelEnd, forward, this );
+ }
+ else
+ {
+ if (m_fireLast !=0)
+ {
+ m_OnLoseTarget.FireOutput(this, this);
+ }
+ m_fireLast = 0;
+ }
+ }
+ else
+ {
+ if (m_fireLast !=0)
+ {
+ m_OnLoseTarget.FireOutput(this, this);
+ }
+ m_fireLast = 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Start of firing sequence. By default, just fire now.
+// Input : &barrelEnd -
+// &forward -
+// *pAttacker -
+//-----------------------------------------------------------------------------
+void CFuncTank::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
+{
+ if ( m_fireLast != 0 )
+ {
+ int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate;
+
+ if ( bulletCount > 0 )
+ {
+ Fire( bulletCount, barrelEnd, forward, pAttacker );
+ m_fireLast = gpGlobals->curtime;
+ }
+ }
+ else
+ {
+ m_fireLast = gpGlobals->curtime;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Fire targets and spawn sprites.
+// Input : bulletCount -
+// barrelEnd -
+// forward -
+// pAttacker -
+//-----------------------------------------------------------------------------
+void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
+{
+ if ( m_iszSpriteSmoke != NULL_STRING )
+ {
+ CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE );
+ pSprite->AnimateAndDie( random->RandomFloat( 15.0, 20.0 ) );
+ pSprite->SetTransparency( kRenderTransAlpha, m_clrRender->r, m_clrRender->g, m_clrRender->b, 255, kRenderFxNone );
+
+ Vector vecVelocity( 0, 0, random->RandomFloat(40, 80) );
+ pSprite->SetAbsVelocity( vecVelocity );
+ pSprite->SetScale( m_spriteScale );
+ }
+ if ( m_iszSpriteFlash != NULL_STRING )
+ {
+ CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE );
+ pSprite->AnimateAndDie( 60 );
+ pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
+ pSprite->SetScale( m_spriteScale );
+ }
+
+ m_OnFire.FireOutput(this, this);
+}
+
+
+void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, trace_t &tr )
+{
+ Vector forward, right, up;
+
+ AngleVectors( GetAbsAngles(), &forward, &right, &up );
+ // get circular gaussian spread
+ float x, y, z;
+ do {
+ x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
+ y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
+ z = x*x+y*y;
+ } while (z > 1);
+ Vector vecDir = vecForward +
+ x * vecSpread.x * right +
+ y * vecSpread.y * up;
+ Vector vecEnd;
+
+ vecEnd = vecStart + vecDir * MAX_TRACE_LENGTH;
+ UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+}
+
+
+void CFuncTank::StartRotSound( void )
+{
+ if ( m_spawnflags & SF_TANK_SOUNDON )
+ return;
+ m_spawnflags |= SF_TANK_SOUNDON;
+
+ if ( m_soundLoopRotate != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this );
+ filter.MakeReliable();
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_STATIC;
+ ep.m_pSoundName = (char*)STRING(m_soundLoopRotate);
+ ep.m_flVolume = 0.85f;
+ ep.m_SoundLevel = SNDLVL_NORM;
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+ if ( m_soundStartRotate != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_BODY;
+ ep.m_pSoundName = (char*)STRING(m_soundStartRotate);
+ ep.m_flVolume = 1.0f;
+ ep.m_SoundLevel = SNDLVL_NORM;
+
+ EmitSound( filter, entindex(), ep );
+ }
+}
+
+
+void CFuncTank::StopRotSound( void )
+{
+ if ( m_spawnflags & SF_TANK_SOUNDON )
+ {
+ if ( m_soundLoopRotate != NULL_STRING )
+ {
+ StopSound( entindex(), CHAN_STATIC, (char*)STRING(m_soundLoopRotate) );
+ }
+ if ( m_soundStopRotate != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_BODY;
+ ep.m_pSoundName = (char*)STRING(m_soundStopRotate);
+ ep.m_flVolume = 1.0f;
+ ep.m_SoundLevel = SNDLVL_NORM;
+
+ EmitSound( filter, entindex(), ep );
+ }
+ }
+ m_spawnflags &= ~SF_TANK_SOUNDON;
+}
+
+// #############################################################################
+// CFuncTankGun
+// #############################################################################
+class CFuncTankGun : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankGun, CFuncTank );
+
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
+};
+LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun );
+
+void CFuncTankGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
+{
+ int i;
+
+ for ( i = 0; i < bulletCount; i++ )
+ {
+ switch( m_bulletType )
+ {
+ case TANK_BULLET_SMALL:
+ FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], MAX_TRACE_LENGTH, m_iSmallAmmoType, 1, -1, -1, m_iBulletDamage, pAttacker );
+ break;
+
+ case TANK_BULLET_MEDIUM:
+ FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], MAX_TRACE_LENGTH, m_iMediumAmmoType, 1, -1, -1, m_iBulletDamage, pAttacker );
+ break;
+
+ case TANK_BULLET_LARGE:
+ FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], MAX_TRACE_LENGTH, m_iLargeAmmoType, 1, -1, -1, m_iBulletDamage, pAttacker );
+ break;
+ default:
+ case TANK_BULLET_NONE:
+ break;
+ }
+ }
+ CFuncTank::Fire( bulletCount, barrelEnd, forward, pAttacker );
+}
+
+// #############################################################################
+// CFuncTankPulseLaser
+// #############################################################################
+class CFuncTankPulseLaser : public CFuncTankGun
+{
+public:
+ DECLARE_CLASS( CFuncTankPulseLaser, CFuncTankGun );
+ DECLARE_DATADESC();
+
+ void Precache();
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
+
+ float m_flPulseSpeed;
+ float m_flPulseWidth;
+ color32 m_flPulseColor;
+ float m_flPulseLife;
+ float m_flPulseLag;
+ string_t m_sPulseFireSound;
+};
+LINK_ENTITY_TO_CLASS( func_tankpulselaser, CFuncTankPulseLaser );
+
+BEGIN_DATADESC( CFuncTankPulseLaser )
+
+ DEFINE_KEYFIELD( m_flPulseSpeed, FIELD_FLOAT, "PulseSpeed" ),
+ DEFINE_KEYFIELD( m_flPulseWidth, FIELD_FLOAT, "PulseWidth" ),
+ DEFINE_KEYFIELD( m_flPulseColor, FIELD_COLOR32, "PulseColor" ),
+ DEFINE_KEYFIELD( m_flPulseLife, FIELD_FLOAT, "PulseLife" ),
+ DEFINE_KEYFIELD( m_flPulseLag, FIELD_FLOAT, "PulseLag" ),
+ DEFINE_KEYFIELD( m_sPulseFireSound, FIELD_SOUNDNAME, "PulseFireSound" ),
+
+END_DATADESC()
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CFuncTankPulseLaser::Precache(void)
+{
+ UTIL_PrecacheOther( "grenade_beam" );
+
+ if ( m_sPulseFireSound != NULL_STRING )
+ {
+ PrecacheScriptSound( STRING(m_sPulseFireSound) );
+ }
+ BaseClass::Precache();
+}
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CFuncTankPulseLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker )
+{
+ // --------------------------------------------------
+ // Get direction vectors for spread
+ // --------------------------------------------------
+ Vector vecUp = Vector(0,0,1);
+ Vector vecRight;
+ CrossProduct ( vecForward, vecUp, vecRight );
+ CrossProduct ( vecForward, -vecRight, vecUp );
+
+ for ( int i = 0; i < bulletCount; i++ )
+ {
+ // get circular gaussian spread
+ float x, y, z;
+ do {
+ x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
+ y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5);
+ z = x*x+y*y;
+ } while (z > 1);
+
+ Vector vecDir = vecForward + x * gTankSpread[m_spread].x * vecRight + y * gTankSpread[m_spread].y * vecUp;
+
+ CGrenadeBeam *pPulse = CGrenadeBeam::Create( pAttacker, barrelEnd);
+ pPulse->Format(m_flPulseColor, m_flPulseWidth);
+ pPulse->Shoot(vecDir,m_flPulseSpeed,m_flPulseLife,m_flPulseLag,m_iBulletDamage);
+
+ if ( m_sPulseFireSound != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this, 0.6f );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_WEAPON;
+ ep.m_pSoundName = (char*)STRING(m_sPulseFireSound);
+ ep.m_flVolume = 1.0f;
+ ep.m_SoundLevel = ATTN_TO_SNDLVL( 0.6 );
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+ }
+ CFuncTank::Fire( bulletCount, barrelEnd, vecForward, pAttacker );
+}
+
+// #############################################################################
+// CFuncTankLaser
+// #############################################################################
+class CFuncTankLaser : public CFuncTank
+{
+ DECLARE_CLASS( CFuncTankLaser, CFuncTank );
+public:
+ void Activate( void );
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
+ void Think( void );
+ CEnvLaser *GetLaser( void );
+ void UpdateOnRemove( void );
+
+ DECLARE_DATADESC();
+
+private:
+ CEnvLaser *m_pLaser;
+ float m_laserTime;
+ string_t m_iszLaserName;
+};
+LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser );
+
+BEGIN_DATADESC( CFuncTankLaser )
+
+ DEFINE_KEYFIELD( m_iszLaserName, FIELD_STRING, "laserentity" ),
+
+ DEFINE_FIELD( m_pLaser, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_laserTime, FIELD_TIME ),
+
+END_DATADESC()
+
+
+void CFuncTankLaser::Activate( void )
+{
+ BaseClass::Activate();
+
+ if ( !GetLaser() )
+ {
+ UTIL_Remove(this);
+ Warning( "Laser tank with no env_laser!\n" );
+ }
+ else
+ {
+ m_pLaser->TurnOff();
+ }
+}
+
+void CFuncTankLaser::UpdateOnRemove( void )
+{
+ if( GetLaser() )
+ {
+ m_pLaser->TurnOff();
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+CEnvLaser *CFuncTankLaser::GetLaser( void )
+{
+ if ( m_pLaser )
+ return m_pLaser;
+
+ CBaseEntity *pLaser = gEntList.FindEntityByName( NULL, m_iszLaserName );
+ while ( pLaser )
+ {
+ // Found the landmark
+ if ( FClassnameIs( pLaser, "env_laser" ) )
+ {
+ m_pLaser = (CEnvLaser *)pLaser;
+ break;
+ }
+ else
+ {
+ pLaser = gEntList.FindEntityByName( pLaser, m_iszLaserName );
+ }
+ }
+
+ return m_pLaser;
+}
+
+
+void CFuncTankLaser::Think( void )
+{
+ if ( m_pLaser && (gpGlobals->curtime > m_laserTime) )
+ m_pLaser->TurnOff();
+
+ CFuncTank::Think();
+}
+
+
+void CFuncTankLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
+{
+ int i;
+ trace_t tr;
+
+ if ( GetLaser() )
+ {
+ for ( i = 0; i < bulletCount; i++ )
+ {
+ m_pLaser->SetLocalOrigin( barrelEnd );
+ TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr );
+
+ m_laserTime = gpGlobals->curtime;
+ m_pLaser->TurnOn();
+ m_pLaser->SetFireTime( gpGlobals->curtime - 1.0 );
+ m_pLaser->FireAtPoint( tr );
+ m_pLaser->SetNextThink( TICK_NEVER_THINK );
+ }
+ CFuncTank::Fire( bulletCount, barrelEnd, forward, this );
+ }
+}
+
+class CFuncTankRocket : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankRocket, CFuncTank );
+
+ void Precache( void );
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
+};
+LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket );
+
+void CFuncTankRocket::Precache( void )
+{
+ UTIL_PrecacheOther( "rpg_rocket" );
+ CFuncTank::Precache();
+}
+
+
+
+void CFuncTankRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
+{
+ int i;
+
+ for ( i = 0; i < bulletCount; i++ )
+ {
+ CBaseEntity *pRocket = CBaseEntity::Create( "rpg_rocket", barrelEnd, GetAbsAngles(), this );
+ pRocket->SetAbsAngles( GetAbsAngles() );
+ }
+ CFuncTank::Fire( bulletCount, barrelEnd, forward, this );
+}
+
+
+class CFuncTankMortar : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankMortar, CFuncTank );
+
+ void Precache( void );
+ void FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker );
+ void ShootGun(void);
+
+ // Input handlers.
+ void InputShootGun( inputdata_t &inputdata );
+
+ DECLARE_DATADESC();
+
+ int m_Magnitude;
+ float m_fireDelay;
+ string_t m_fireStartSound;
+ string_t m_fireEndSound;
+
+ // store future firing event
+ CBaseEntity *m_pAttacker;
+};
+
+LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar );
+
+BEGIN_DATADESC( CFuncTankMortar )
+
+ DEFINE_KEYFIELD( m_Magnitude, FIELD_INTEGER, "iMagnitude" ),
+ DEFINE_KEYFIELD( m_fireDelay, FIELD_FLOAT, "firedelay" ),
+ DEFINE_KEYFIELD( m_fireStartSound, FIELD_STRING, "firestartsound" ),
+ DEFINE_KEYFIELD( m_fireEndSound, FIELD_STRING, "fireendsound" ),
+
+ DEFINE_FIELD( m_pAttacker, FIELD_CLASSPTR ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "ShootGun", InputShootGun ),
+
+END_DATADESC()
+
+
+
+void CFuncTankMortar::Precache( void )
+{
+ if ( m_fireStartSound != NULL_STRING )
+ PrecacheScriptSound( STRING(m_fireStartSound) );
+ if ( m_fireEndSound != NULL_STRING )
+ PrecacheScriptSound( STRING(m_fireEndSound) );
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler to make the tank shoot.
+//-----------------------------------------------------------------------------
+void CFuncTankMortar::InputShootGun( inputdata_t &inputdata )
+{
+ ShootGun();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTankMortar::ShootGun( void )
+{
+ Vector forward;
+ AngleVectors( GetLocalAngles(), &forward );
+ UpdateMatrix();
+ forward = m_parentMatrix.ApplyRotation( forward );
+
+ // use cached firing state
+ Fire( 1, WorldBarrelPosition(), forward, m_pAttacker );
+}
+
+
+void CFuncTankMortar::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
+{
+ if ( m_fireLast != 0 )
+ {
+ int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate;
+ // Only create 1 explosion
+ if ( bulletCount > 0 )
+ {
+ // fire in "firedelay" seconds
+ if ( m_fireStartSound != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_WEAPON;
+ ep.m_pSoundName = (char*)STRING(m_fireStartSound);
+ ep.m_flVolume = 1.0f;
+ ep.m_SoundLevel = SNDLVL_NORM;
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+ if ( m_fireDelay != 0 )
+ {
+ g_EventQueue.AddEvent( this, "ShootGun", m_fireDelay, pAttacker, this, 0 );
+ }
+ else
+ {
+ ShootGun();
+ }
+ m_fireLast = gpGlobals->curtime;
+ }
+ }
+ else
+ {
+ m_fireLast = gpGlobals->curtime;
+ }
+}
+
+void CFuncTankMortar::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker )
+{
+ if ( m_fireEndSound != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_WEAPON;
+ ep.m_pSoundName = STRING(m_fireEndSound);
+ ep.m_flVolume = 1.0f;
+ ep.m_SoundLevel = SNDLVL_NORM;
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+ trace_t tr;
+
+ TankTrace( barrelEnd, vecForward, gTankSpread[m_spread], tr );
+
+ const CBaseEntity * ent = NULL;
+ if ( g_pGameRules->IsMultiplayer() )
+ {
+ // temp remove suppress host
+ ent = te->GetSuppressHost();
+ te->SetSuppressHost( NULL );
+ }
+
+ ExplosionCreate( tr.endpos, GetAbsAngles(), this, m_Magnitude, 0, true );
+
+ if ( g_pGameRules->IsMultiplayer() )
+ {
+ te->SetSuppressHost( (CBaseEntity *) ent );
+ }
+
+ BaseClass::Fire( bulletCount, barrelEnd, vecForward, this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Func tank that fires physics cannisters placed on it
+//-----------------------------------------------------------------------------
+class CFuncTankPhysCannister : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankPhysCannister, CFuncTank );
+ DECLARE_DATADESC();
+
+ void Activate( void );
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker );
+
+protected:
+ string_t m_iszBarrelVolume;
+ CHandle<CBaseTrigger> m_hBarrelVolume;
+};
+
+LINK_ENTITY_TO_CLASS( func_tankphyscannister, CFuncTankPhysCannister );
+
+BEGIN_DATADESC( CFuncTankPhysCannister )
+
+ DEFINE_KEYFIELD( m_iszBarrelVolume, FIELD_STRING, "barrel_volume" ),
+ DEFINE_FIELD( m_hBarrelVolume, FIELD_EHANDLE ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTankPhysCannister::Activate( void )
+{
+ BaseClass::Activate();
+
+ m_hBarrelVolume = NULL;
+
+ // Find our barrel volume
+ if ( m_iszBarrelVolume != NULL_STRING )
+ {
+ m_hBarrelVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszBarrelVolume ) );
+ }
+
+ if ( !m_hBarrelVolume )
+ {
+ Msg("ERROR: Couldn't find barrel volume for func_tankphyscannister %s.\n", STRING(GetEntityName()) );
+ UTIL_Remove( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTankPhysCannister::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
+{
+ Assert( m_hBarrelVolume );
+
+ // Do we have a cannister in our barrel volume?
+ CPhysicsCannister *pCannister = (CPhysicsCannister *)m_hBarrelVolume->GetTouchedEntityOfType( "physics_cannister" );
+ if ( !pCannister )
+ {
+ // Play a no-ammo sound
+ return;
+ }
+
+ // Fire the cannister!
+ pCannister->CannisterFire( pAttacker );
+}
+
diff --git a/game/server/hl1/hl1_grenade_mp5.cpp b/game/server/hl1/hl1_grenade_mp5.cpp
new file mode 100644
index 0000000..9a72c9f
--- /dev/null
+++ b/game/server/hl1/hl1_grenade_mp5.cpp
@@ -0,0 +1,160 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "hl1_grenade_mp5.h"
+#include "hl1mp_weapon_mp5.h"
+#include "soundent.h"
+#include "decals.h"
+#include "shake.h"
+#include "smoke_trail.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "world.h"
+
+extern short g_sModelIndexFireball;
+extern short g_sModelIndexWExplosion;
+
+extern ConVar sk_plr_dmg_mp5_grenade;
+extern ConVar sk_max_mp5_grenade;
+extern ConVar sk_mp5_grenade_radius;
+
+BEGIN_DATADESC( CGrenadeMP5 )
+ // SR-BUGBUG: These are borked!!!!
+// float m_fSpawnTime;
+
+ // Function pointers
+ DEFINE_ENTITYFUNC( GrenadeMP5Touch ),
+
+ DEFINE_FIELD( m_fSpawnTime, FIELD_TIME ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( grenade_mp5, CGrenadeMP5 );
+
+void CGrenadeMP5::Spawn( void )
+{
+ Precache( );
+ SetSolid( SOLID_BBOX );
+ SetMoveType( MOVETYPE_FLY );
+ AddFlag( FL_GRENADE );
+
+ SetModel( "models/grenade.mdl" );
+ //UTIL_SetSize(this, Vector(-3, -3, -3), Vector(3, 3, 3));
+ UTIL_SetSize(this, Vector(0, 0, 0), Vector(0, 0, 0));
+
+ SetUse( &CBaseGrenade::DetonateUse );
+ SetTouch( &CGrenadeMP5::GrenadeMP5Touch );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ m_flDamage = sk_plr_dmg_mp5_grenade.GetFloat();
+ m_DmgRadius = sk_mp5_grenade_radius.GetFloat();
+ m_takedamage = DAMAGE_YES;
+ m_bIsLive = true;
+ m_iHealth = 1;
+
+ SetGravity( UTIL_ScaleForGravity( 400 ) ); // use a lower gravity for grenades to make them easier to see
+ SetFriction( 0.8 );
+
+ SetSequence( 0 );
+
+ m_fSpawnTime = gpGlobals->curtime;
+}
+
+
+void CGrenadeMP5::Event_Killed( CBaseEntity *pInflictor, CBaseEntity *pAttacker, float flDamage, int bitsDamageType )
+{
+ Detonate( );
+}
+
+
+void CGrenadeMP5::GrenadeMP5Touch( CBaseEntity *pOther )
+{
+ if ( !pOther->IsSolid() )
+ return;
+
+ // If I'm live go ahead and blow up
+ if (m_bIsLive)
+ {
+ Detonate();
+ }
+ else
+ {
+ // If I'm not live, only blow up if I'm hitting an chacter that
+ // is not the owner of the weapon
+ CBaseCombatCharacter *pBCC = ToBaseCombatCharacter( pOther );
+ if (pBCC && GetThrower() != pBCC)
+ {
+ m_bIsLive = true;
+ Detonate();
+ }
+ }
+}
+
+void CGrenadeMP5::Detonate(void)
+{
+ if (!m_bIsLive)
+ {
+ return;
+ }
+ m_bIsLive = false;
+ m_takedamage = DAMAGE_NO;
+
+ CPASFilter filter( GetAbsOrigin() );
+
+ te->Explosion( filter, 0.0,
+ &GetAbsOrigin(),
+ GetWaterLevel() == 0 ? g_sModelIndexFireball : g_sModelIndexWExplosion,
+ (m_flDamage - 50) * .60,
+ 15,
+ TE_EXPLFLAG_NONE,
+ m_DmgRadius,
+ m_flDamage );
+
+ trace_t tr;
+ tr = CBaseEntity::GetTouchTrace();
+
+ if ( (tr.m_pEnt != GetWorldEntity()) || (tr.hitbox != 0) )
+ {
+ // non-world needs smaller decals
+ UTIL_DecalTrace( &tr, "SmallScorch");
+ }
+ else
+ {
+ UTIL_DecalTrace( &tr, "Scorch" );
+ }
+
+ CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), BASEGRENADE_EXPLOSION_VOLUME, 3.0 );
+
+ RadiusDamage ( CTakeDamageInfo( this, GetThrower(), m_flDamage, DMG_BLAST ), GetAbsOrigin(), m_flDamage * 2.5, CLASS_NONE, NULL );
+
+ CPASAttenuationFilter filter2( this );
+ EmitSound( filter2, entindex(), "GrenadeMP5.Detonate" );
+
+ if ( GetWaterLevel() == 0 )
+ {
+ int sparkCount = random->RandomInt( 0,3 );
+ QAngle angles;
+ VectorAngles( tr.plane.normal, angles );
+
+ for ( int i = 0; i < sparkCount; i++ )
+ Create( "spark_shower", GetAbsOrigin(), angles, NULL );
+ }
+
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeMP5::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheModel( "models/grenade.mdl" );
+
+ PrecacheScriptSound( "GrenadeMP5.Detonate" );
+}
diff --git a/game/server/hl1/hl1_grenade_mp5.h b/game/server/hl1/hl1_grenade_mp5.h
new file mode 100644
index 0000000..8d5c592
--- /dev/null
+++ b/game/server/hl1/hl1_grenade_mp5.h
@@ -0,0 +1,42 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Projectile shot from the MP5
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef GRENADEMP5_H
+#define GRENADEMP5_H
+
+#include "hl1_basegrenade.h"
+
+#define MAX_MP5_NO_COLLIDE_TIME 0.2
+
+class SmokeTrail;
+class CWeaponMP5;
+
+class CGrenadeMP5 : public CHL1BaseGrenade
+{
+ DECLARE_CLASS( CGrenadeMP5, CHL1BaseGrenade );
+public:
+
+ float m_fSpawnTime;
+
+ void Spawn( void );
+ void Precache( void );
+ void GrenadeMP5Touch( CBaseEntity *pOther );
+ void Event_Killed( CBaseEntity *pInflictor, CBaseEntity *pAttacker, float flDamage, int bitsDamageType );
+
+public:
+ void EXPORT Detonate(void);
+
+ DECLARE_DATADESC();
+};
+
+#endif //GRENADEMP5_H
diff --git a/game/server/hl1/hl1_grenade_spit.cpp b/game/server/hl1/hl1_grenade_spit.cpp
new file mode 100644
index 0000000..1dc1d84
--- /dev/null
+++ b/game/server/hl1/hl1_grenade_spit.cpp
@@ -0,0 +1,171 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "hl1_grenade_spit.h"
+#include "soundent.h"
+#include "decals.h"
+
+#include "smoke_trail.h"
+#include "hl2_shareddefs.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+
+ConVar sk_bullsquid_dmg_spit ( "sk_bullsquid_dmg_spit", "0" );
+
+BEGIN_DATADESC( CGrenadeSpit )
+
+ // Function pointers
+ DEFINE_THINKFUNC( SpitThink ),
+ DEFINE_ENTITYFUNC( GrenadeSpitTouch ),
+
+ //DEFINE_FIELD( m_nSquidSpitSprite, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_fSpitDeathTime, FIELD_TIME ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( grenade_spit, CGrenadeSpit );
+
+void CGrenadeSpit::Spawn( void )
+{
+ Precache( );
+ SetSolid( SOLID_BBOX );
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+
+ // FIXME, if these is a sprite, then we need a base class derived from CSprite rather than
+ // CBaseAnimating. pev->scale becomes m_flSpriteScale in that case.
+ SetModel( "models/spitball_large.mdl" );
+ UTIL_SetSize(this, Vector(-3, -3, -3), Vector(3, 3, 3));
+
+ m_nRenderMode = kRenderTransAdd;
+ SetRenderColor( 255, 255, 255, 255 );
+ m_nRenderFX = kRenderFxNone;
+
+ SetThink( &CGrenadeSpit::SpitThink );
+ SetUse( &CBaseGrenade::DetonateUse );
+ SetTouch( &CGrenadeSpit::GrenadeSpitTouch );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ m_flDamage = sk_bullsquid_dmg_spit.GetFloat();
+ m_DmgRadius = 60.0f;
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = 1;
+
+ SetGravity( SPIT_GRAVITY );
+ SetFriction( 0.8 );
+ SetSequence( 1 );
+
+ SetCollisionGroup( HL2COLLISION_GROUP_SPIT );
+}
+
+
+void CGrenadeSpit::SetSpitSize(int nSize)
+{
+ switch (nSize)
+ {
+ case SPIT_LARGE:
+ {
+ SetModel( "models/spitball_large.mdl" );
+ break;
+ }
+ case SPIT_MEDIUM:
+ {
+ SetModel( "models/spitball_medium.mdl" );
+ break;
+ }
+ case SPIT_SMALL:
+ {
+ SetModel( "models/spitball_small.mdl" );
+ break;
+ }
+ }
+}
+
+void CGrenadeSpit::Event_Killed( const CTakeDamageInfo &info )
+{
+ Detonate( );
+}
+
+void CGrenadeSpit::GrenadeSpitTouch( CBaseEntity *pOther )
+{
+ if (m_fSpitDeathTime != 0)
+ {
+ return;
+ }
+ if ( pOther->GetCollisionGroup() == HL2COLLISION_GROUP_SPIT)
+ {
+ return;
+ }
+ if ( !pOther->m_takedamage )
+ {
+
+ // make a splat on the wall
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 10, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ UTIL_DecalTrace(&tr, "BeerSplash" );
+
+ // make some flecks
+ CPVSFilter filter( tr.endpos );
+ te->SpriteSpray( filter, 0.0,
+ &tr.endpos, &tr.plane.normal, m_nSquidSpitSprite, random->RandomInt( 90, 160 ), 50, random->RandomInt ( 5, 15 ) );
+ }
+ else
+ {
+ RadiusDamage ( CTakeDamageInfo( this, GetThrower(), m_flDamage, DMG_BLAST ), GetAbsOrigin(), m_DmgRadius, CLASS_NONE, NULL );
+ }
+
+ Detonate();
+}
+
+void CGrenadeSpit::SpitThink( void )
+{
+ if (m_fSpitDeathTime != 0 &&
+ m_fSpitDeathTime < gpGlobals->curtime)
+ {
+ UTIL_Remove( this );
+ }
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+void CGrenadeSpit::Detonate(void)
+{
+ m_takedamage = DAMAGE_NO;
+
+ int iPitch;
+
+ // splat sound
+ iPitch = random->RandomFloat( 90, 110 );
+
+ EmitSound( "GrenadeSpit.Acid" );
+ EmitSound( "GrenadeSpit.Hit" );
+
+ UTIL_Remove( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CGrenadeSpit::Precache( void )
+{
+ m_nSquidSpitSprite = PrecacheModel("sprites/bigspit.vmt");// client side spittle.
+
+ PrecacheModel("models/spitball_large.mdl");
+ PrecacheModel("models/spitball_medium.mdl");
+ PrecacheModel("models/spitball_small.mdl");
+
+ PrecacheScriptSound( "GrenadeSpit.Acid" );
+ PrecacheScriptSound( "GrenadeSpit.Hit" );
+
+}
+
+
+CGrenadeSpit::CGrenadeSpit(void)
+{
+}
diff --git a/game/server/hl1/hl1_grenade_spit.h b/game/server/hl1/hl1_grenade_spit.h
new file mode 100644
index 0000000..70ff6c7
--- /dev/null
+++ b/game/server/hl1/hl1_grenade_spit.h
@@ -0,0 +1,49 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Projectile shot by bullsquid
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef GRENADESPIT_H
+#define GRENADESPIT_H
+
+#include "hl1_basegrenade.h"
+
+enum SpitSize_e
+{
+ SPIT_SMALL,
+ SPIT_MEDIUM,
+ SPIT_LARGE,
+};
+
+#define SPIT_GRAVITY 0.9
+
+class CGrenadeSpit : public CHL1BaseGrenade
+{
+public:
+ DECLARE_CLASS( CGrenadeSpit, CHL1BaseGrenade );
+
+ void Spawn( void );
+ void Precache( void );
+ void SpitThink( void );
+ void GrenadeSpitTouch( CBaseEntity *pOther );
+ void Event_Killed( const CTakeDamageInfo &info );
+ void SetSpitSize(int nSize);
+
+ int m_nSquidSpitSprite;
+ float m_fSpitDeathTime; // If non-zero won't detonate
+
+ void EXPORT Detonate(void);
+ CGrenadeSpit(void);
+
+ DECLARE_DATADESC();
+};
+
+#endif //GRENADESPIT_H
diff --git a/game/server/hl1/hl1_item_ammo.cpp b/game/server/hl1/hl1_item_ammo.cpp
new file mode 100644
index 0000000..e5d44bc
--- /dev/null
+++ b/game/server/hl1/hl1_item_ammo.cpp
@@ -0,0 +1,410 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Ammo boxes for HL1
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "player.h"
+#include "gamerules.h"
+#include "items.h"
+#include "hl1_items.h"
+
+
+// ========================================================================
+// >> Crossbow bolts
+// ========================================================================
+#define AMMO_CROSSBOW_GIVE 5
+#define AMMO_CROSSBOW_MODEL "models/w_crossbow_clip.mdl"
+
+class CCrossbowAmmo : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CCrossbowAmmo, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_CROSSBOW_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel( AMMO_CROSSBOW_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_CROSSBOW_GIVE, "XBowBolt" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_crossbow, CCrossbowAmmo);
+PRECACHE_REGISTER(ammo_crossbow);
+
+
+// ========================================================================
+// >> Egon ammo
+// ========================================================================
+#define AMMO_EGON_GIVE 20
+#define AMMO_EGON_MODEL "models/w_chainammo.mdl"
+
+class CEgonAmmo : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CEgonAmmo, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_EGON_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_EGON_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_EGON_GIVE, "Uranium" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_egonclip, CEgonAmmo);
+PRECACHE_REGISTER(ammo_egonclip);
+
+
+// ========================================================================
+// >> Gauss ammo
+// ========================================================================
+#define AMMO_GAUSS_GIVE 20
+#define AMMO_GAUSS_MODEL "models/w_gaussammo.mdl"
+
+class CGaussAmmo : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CGaussAmmo, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_GAUSS_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_GAUSS_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_GAUSS_GIVE, "Uranium" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_gaussclip, CGaussAmmo);
+PRECACHE_REGISTER(ammo_gaussclip);
+
+
+// ========================================================================
+// >> Glock ammo
+// ========================================================================
+#define AMMO_GLOCK_GIVE 18
+#define AMMO_GLOCK_MODEL "models/w_9mmclip.mdl"
+
+class CGlockAmmo : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CGlockAmmo, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_GLOCK_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_GLOCK_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_GLOCK_GIVE, "9mmRound" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_glockclip, CGlockAmmo);
+PRECACHE_REGISTER(ammo_glockclip);
+LINK_ENTITY_TO_CLASS(ammo_9mmclip, CGlockAmmo);
+PRECACHE_REGISTER(ammo_9mmclip);
+
+
+// ========================================================================
+// >> MP5 ammo
+// ========================================================================
+#define AMMO_MP5_GIVE 50
+#define AMMO_MP5_MODEL "models/w_9mmARclip.mdl"
+
+class CMP5AmmoClip : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CMP5AmmoClip, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_MP5_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_MP5_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_MP5_GIVE, "9mmRound" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_mp5clip, CMP5AmmoClip);
+PRECACHE_REGISTER(ammo_mp5clip);
+LINK_ENTITY_TO_CLASS(ammo_9mmar, CMP5AmmoClip);
+PRECACHE_REGISTER(ammo_9mmar);
+
+
+// ========================================================================
+// >> MP5Chain (?) ammo
+// ========================================================================
+#define AMMO_MP5CHAIN_GIVE 200
+#define AMMO_MP5CHAIN_MODEL "models/w_chainammo.mdl"
+
+class CMP5Chainammo : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CMP5Chainammo, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_MP5CHAIN_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_MP5CHAIN_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_MP5CHAIN_GIVE, "9mmRound" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_9mmbox, CMP5Chainammo);
+PRECACHE_REGISTER(ammo_9mmbox);
+
+
+// ========================================================================
+// >> MP5 grenades
+// ========================================================================
+#define AMMO_MP5GRENADE_GIVE 2
+#define AMMO_MP5GRENADE_MODEL "models/w_ARgrenade.mdl"
+
+class CMP5AmmoGrenade : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CMP5AmmoGrenade, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_MP5GRENADE_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_MP5GRENADE_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_MP5GRENADE_GIVE, "MP5_Grenade" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_mp5grenades, CMP5AmmoGrenade);
+PRECACHE_REGISTER(ammo_mp5grenades);
+LINK_ENTITY_TO_CLASS(ammo_argrenades, CMP5AmmoGrenade);
+PRECACHE_REGISTER(ammo_argrenades);
+
+
+// ========================================================================
+// >> 357 ammo
+// ========================================================================
+#define AMMO_357_GIVE 6
+#define AMMO_357_MODEL "models/w_357ammobox.mdl"
+
+class CPythonAmmo : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CPythonAmmo, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_357_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_357_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_357_GIVE, "357Round" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_357, CPythonAmmo);
+PRECACHE_REGISTER(ammo_357);
+
+
+// ========================================================================
+// >> RPG rockets
+// ========================================================================
+#define AMMO_RPG_GIVE 1
+#define AMMO_RPG_MODEL "models/w_rpgammo.mdl"
+
+class CRpgAmmo : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CRpgAmmo, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_RPG_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_RPG_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ int nGive;
+
+ if ( g_pGameRules->IsMultiplayer() )
+ {
+ // hand out more ammo per rocket in multiplayer.
+ nGive = AMMO_RPG_GIVE * 2;
+ }
+ else
+ {
+ nGive = AMMO_RPG_GIVE;
+ }
+
+ if (pPlayer->GiveAmmo( nGive, "RPG_Rocket" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_rpgclip, CRpgAmmo);
+PRECACHE_REGISTER(ammo_rpgclip);
+
+
+// ========================================================================
+// >> Shotgun ammo
+// ========================================================================
+#define AMMO_SHOTGUN_GIVE 12
+#define AMMO_SHOTGUN_MODEL "models/w_shotbox.mdl"
+
+class CShotgunAmmo : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CShotgunAmmo, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( AMMO_SHOTGUN_MODEL );
+ BaseClass::Spawn();
+ }
+ void Precache( void )
+ {
+ PrecacheModel ( AMMO_SHOTGUN_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if (pPlayer->GiveAmmo( AMMO_SHOTGUN_GIVE, "Buckshot" ) )
+ {
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+ return true;
+ }
+ return false;
+ }
+};
+LINK_ENTITY_TO_CLASS(ammo_buckshot, CShotgunAmmo);
+PRECACHE_REGISTER(ammo_buckshot);
diff --git a/game/server/hl1/hl1_item_battery.cpp b/game/server/hl1/hl1_item_battery.cpp
new file mode 100644
index 0000000..6cddacb
--- /dev/null
+++ b/game/server/hl1/hl1_item_battery.cpp
@@ -0,0 +1,78 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Handling for the suit batteries.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "player.h"
+#include "basecombatweapon.h"
+#include "gamerules.h"
+#include "items.h"
+#include "engine/IEngineSound.h"
+#include "hl1_items.h"
+
+
+#define BATTERY_MODEL "models/w_battery.mdl"
+
+ConVar sk_battery( "sk_battery","0" );
+
+class CItemBattery : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CItemBattery, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache( );
+ SetModel( BATTERY_MODEL );
+ BaseClass::Spawn( );
+ }
+ void Precache( void )
+ {
+ PrecacheModel( BATTERY_MODEL );
+
+ PrecacheScriptSound( "Item.Pickup" );
+ }
+
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if ((pPlayer->ArmorValue() < MAX_NORMAL_BATTERY) && pPlayer->IsSuitEquipped())
+ {
+ int pct;
+ char szcharge[64];
+
+ pPlayer->IncrementArmorValue( sk_battery.GetFloat(), MAX_NORMAL_BATTERY );
+
+ CPASAttenuationFilter filter( pPlayer, "Item.Pickup" );
+ EmitSound( filter, pPlayer->entindex(), "Item.Pickup" );
+
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "ItemPickup" );
+ WRITE_STRING( GetClassname() );
+ MessageEnd();
+
+
+ // Suit reports new power level
+ // For some reason this wasn't working in release build -- round it.
+ pct = (int)( (float)(pPlayer->ArmorValue() * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5);
+ pct = (pct / 5);
+ if (pct > 0)
+ pct--;
+
+ Q_snprintf( szcharge,sizeof(szcharge),"!HEV_%1dP", pct );
+
+ //UTIL_EmitSoundSuit(edict(), szcharge);
+ pPlayer->SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC);
+ return true;
+ }
+ return false;
+ }
+};
+
+LINK_ENTITY_TO_CLASS(item_battery, CItemBattery);
+PRECACHE_REGISTER(item_battery);
+
diff --git a/game/server/hl1/hl1_item_healthkit.cpp b/game/server/hl1/hl1_item_healthkit.cpp
new file mode 100644
index 0000000..a5834d9
--- /dev/null
+++ b/game/server/hl1/hl1_item_healthkit.cpp
@@ -0,0 +1,384 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "gamerules.h"
+#include "player.h"
+#include "items.h"
+#include "engine/IEngineSound.h"
+#include "hl1_items.h"
+#include "in_buttons.h"
+
+
+ConVar sk_healthkit( "sk_healthkit","0" );
+ConVar sk_healthvial( "sk_healthvial","0" );
+ConVar sk_healthcharger( "sk_healthcharger","0" );
+
+//-----------------------------------------------------------------------------
+// Small health kit. Heals the player when picked up.
+//-----------------------------------------------------------------------------
+class CHealthKit : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CHealthKit, CHL1Item );
+
+ void Spawn( void );
+ void Precache( void );
+ bool MyTouch( CBasePlayer *pPlayer );
+};
+
+LINK_ENTITY_TO_CLASS( item_healthkit, CHealthKit );
+PRECACHE_REGISTER(item_healthkit);
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHealthKit::Spawn( void )
+{
+ Precache();
+ SetModel( "models/w_medkit.mdl" );
+
+ BaseClass::Spawn();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHealthKit::Precache( void )
+{
+ PrecacheModel("models/w_medkit.mdl");
+
+ PrecacheScriptSound( "HealthKit.Touch" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+// Output :
+//-----------------------------------------------------------------------------
+bool CHealthKit::MyTouch( CBasePlayer *pPlayer )
+{
+ if ( pPlayer->TakeHealth( sk_healthkit.GetFloat(), DMG_GENERIC ) )
+ {
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "ItemPickup" );
+ WRITE_STRING( GetClassname() );
+ MessageEnd();
+
+ CPASAttenuationFilter filter( pPlayer, "HealthKit.Touch" );
+ EmitSound( filter, pPlayer->entindex(), "HealthKit.Touch" );
+
+ if ( g_pGameRules->ItemShouldRespawn( this ) )
+ {
+ Respawn();
+ }
+ else
+ {
+ UTIL_Remove(this);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Small dynamically dropped health kit
+//-----------------------------------------------------------------------------
+
+class CHealthVial : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CHealthVial, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache();
+ SetModel( "models/healthvial.mdl" );
+
+ BaseClass::Spawn();
+ }
+
+ void Precache( void )
+ {
+ PrecacheModel("models/healthvial.mdl");
+
+ PrecacheScriptSound( "HealthVial.Touch" );
+ }
+
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if ( pPlayer->TakeHealth( sk_healthvial.GetFloat(), DMG_GENERIC ) )
+ {
+ CSingleUserRecipientFilter user( pPlayer );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "ItemPickup" );
+ WRITE_STRING( GetClassname() );
+ MessageEnd();
+
+ CPASAttenuationFilter filter( pPlayer, "HealthVial.Touch" );
+ EmitSound( filter, pPlayer->entindex(), "HealthVial.Touch" );
+
+ if ( g_pGameRules->ItemShouldRespawn( this ) )
+ {
+ Respawn();
+ }
+ else
+ {
+ UTIL_Remove(this);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+};
+
+LINK_ENTITY_TO_CLASS( item_healthvial, CHealthVial );
+PRECACHE_REGISTER( item_healthvial );
+
+//-----------------------------------------------------------------------------
+// Wall mounted health kit. Heals the player when used.
+//-----------------------------------------------------------------------------
+class CWallHealth : public CBaseToggle
+{
+public:
+ DECLARE_CLASS( CWallHealth, CBaseToggle );
+
+ void Spawn( );
+ void Precache( void );
+ bool CreateVPhysics(void);
+ void Off(void);
+ void Recharge(void);
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | m_iCaps; }
+
+ float m_flNextCharge;
+ int m_iReactivate ; // DeathMatch Delay until reactvated
+ int m_iJuice;
+ int m_iOn; // 0 = off, 1 = startup, 2 = going
+ float m_flSoundTime;
+ int m_iCaps;
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS(func_healthcharger, CWallHealth);
+
+
+BEGIN_DATADESC( CWallHealth )
+
+ DEFINE_FIELD( m_flNextCharge, FIELD_TIME),
+ DEFINE_FIELD( m_iReactivate, FIELD_INTEGER),
+ DEFINE_FIELD( m_iJuice, FIELD_INTEGER),
+ DEFINE_FIELD( m_iOn, FIELD_INTEGER),
+ DEFINE_FIELD( m_flSoundTime, FIELD_TIME),
+ DEFINE_FIELD( m_iCaps, FIELD_INTEGER ),
+
+ // Function Pointers
+ DEFINE_FUNCTION( Off ),
+ DEFINE_FUNCTION( Recharge ),
+
+END_DATADESC()
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pkvd -
+//-----------------------------------------------------------------------------
+bool CWallHealth::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if (FStrEq(szKeyName, "style") ||
+ FStrEq(szKeyName, "height") ||
+ FStrEq(szKeyName, "value1") ||
+ FStrEq(szKeyName, "value2") ||
+ FStrEq(szKeyName, "value3"))
+ {
+ return(true);
+ }
+ else if (FStrEq(szKeyName, "dmdelay"))
+ {
+ m_iReactivate = atoi(szValue);
+ return(true);
+ }
+
+ return(BaseClass::KeyValue( szKeyName, szValue ));
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWallHealth::Spawn(void)
+{
+ Precache( );
+
+ SetSolid( SOLID_BSP );
+ SetMoveType( MOVETYPE_PUSH );
+
+ SetModel( STRING( GetModelName() ) );
+
+ m_iJuice = sk_healthcharger.GetFloat();
+ SetTextureFrameIndex( 0 );
+
+ m_iCaps = FCAP_CONTINUOUS_USE;
+
+ CreateVPhysics();
+}
+
+//-----------------------------------------------------------------------------
+
+bool CWallHealth::CreateVPhysics(void)
+{
+ VPhysicsInitStatic();
+ return true;
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWallHealth::Precache(void)
+{
+ PrecacheScriptSound( "WallHealth.Deny" );
+ PrecacheScriptSound( "WallHealth.Start" );
+ PrecacheScriptSound( "WallHealth.LoopingContinueCharge" );
+ PrecacheScriptSound( "WallHealth.Recharge" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pActivator -
+// *pCaller -
+// useType -
+// value -
+//-----------------------------------------------------------------------------
+void CWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Make sure that we have a caller
+ if (!pActivator)
+ return;
+ // if it's not a player, ignore
+ if ( !pActivator->IsPlayer() )
+ return;
+
+ // Reset to a state of continuous use.
+ m_iCaps = FCAP_CONTINUOUS_USE;
+
+ // if there is no juice left, turn it off
+ if (m_iJuice <= 0)
+ {
+ Off();
+ SetTextureFrameIndex( 1 );
+ }
+
+ CBasePlayer *pPlayer = ToBasePlayer( pActivator );
+
+ // if the player doesn't have the suit, or there is no juice left, make the deny noise.
+ if ((m_iJuice <= 0) || (!(pPlayer->m_Local.m_bWearingSuit)))
+ {
+ if (m_flSoundTime <= gpGlobals->curtime)
+ {
+ m_flSoundTime = gpGlobals->curtime + 0.62;
+ EmitSound( "WallHealth.Deny" );
+ }
+ return;
+ }
+
+ if( pActivator->GetHealth() >= pActivator->GetMaxHealth() )
+ {
+ CBasePlayer *pPlayer = dynamic_cast<CBasePlayer *>(pActivator);
+
+ if( pPlayer )
+ {
+ pPlayer->m_afButtonPressed &= ~IN_USE;
+ }
+
+ // Make the user re-use me to get started drawing health.
+ m_iCaps = FCAP_IMPULSE_USE;
+
+ EmitSound( "WallHealth.Deny" );
+ return;
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.25 );
+ SetThink(&CWallHealth::Off);
+
+ // Time to recharge yet?
+
+ if (m_flNextCharge >= gpGlobals->curtime)
+ return;
+
+ // Play the on sound or the looping charging sound
+ if (!m_iOn)
+ {
+ m_iOn++;
+ EmitSound( "WallHealth.Start" );
+ m_flSoundTime = 0.56 + gpGlobals->curtime;
+ }
+ if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->curtime))
+ {
+ m_iOn++;
+ CPASAttenuationFilter filter( this, "WallHealth.LoopingContinueCharge" );
+ filter.MakeReliable();
+ EmitSound( filter, entindex(), "WallHealth.LoopingContinueCharge" );
+ }
+
+
+ // charge the player
+ if ( pActivator->TakeHealth( 1, DMG_GENERIC ) )
+ {
+ m_iJuice--;
+ }
+
+ // govern the rate of charge
+ m_flNextCharge = gpGlobals->curtime + 0.1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWallHealth::Recharge(void)
+{
+ EmitSound( "WallHealth.Recharge" );
+ m_iJuice = sk_healthcharger.GetFloat();
+ SetTextureFrameIndex( 0 );
+ SetThink( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWallHealth::Off(void)
+{
+ // Stop looping sound.
+ if (m_iOn > 1)
+ StopSound( "WallHealth.LoopingContinueCharge" );
+
+ m_iOn = 0;
+
+ if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHealthChargerRechargeTime() ) > 0) )
+ {
+ SetNextThink( gpGlobals->curtime + m_iReactivate );
+ SetThink(&CWallHealth::Recharge);
+ }
+ else
+ SetThink( NULL );
+}
+
diff --git a/game/server/hl1/hl1_item_longjump.cpp b/game/server/hl1/hl1_item_longjump.cpp
new file mode 100644
index 0000000..3ecfd9a
--- /dev/null
+++ b/game/server/hl1/hl1_item_longjump.cpp
@@ -0,0 +1,61 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "player.h"
+#include "gamerules.h"
+#include "items.h"
+#include "hl1_items.h"
+#include "hl1_player.h"
+
+
+class CItemLongJump : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CItemLongJump, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache( );
+ SetModel( "models/w_longjump.mdl" );
+ BaseClass::Spawn( );
+
+ CollisionProp()->UseTriggerBounds( true, 16.0f );
+ }
+ void Precache( void )
+ {
+ PrecacheModel ("models/w_longjump.mdl");
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ CHL1_Player *pHL1Player = (CHL1_Player*)pPlayer;
+
+ if ( pHL1Player->m_bHasLongJump == true )
+ {
+ return false;
+ }
+
+ if ( pHL1Player->IsSuitEquipped() )
+ {
+ pHL1Player->m_bHasLongJump = true;// player now has longjump module
+
+ CSingleUserRecipientFilter user( pHL1Player );
+ user.MakeReliable();
+
+ UserMessageBegin( user, "ItemPickup" );
+ WRITE_STRING( STRING(m_iClassname) );
+ MessageEnd();
+
+ UTIL_EmitSoundSuit( pHL1Player->edict(), "!HEV_A1" ); // Play the longjump sound UNDONE: Kelly? correct sound?
+ return true;
+ }
+ return false;
+ }
+};
+
+LINK_ENTITY_TO_CLASS( item_longjump, CItemLongJump );
+PRECACHE_REGISTER(item_longjump);
diff --git a/game/server/hl1/hl1_item_suit.cpp b/game/server/hl1/hl1_item_suit.cpp
new file mode 100644
index 0000000..83644fe
--- /dev/null
+++ b/game/server/hl1/hl1_item_suit.cpp
@@ -0,0 +1,61 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+/*
+===== item_suit.cpp ========================================================
+
+ handling for the player's suit.
+*/
+
+#include "cbase.h"
+#include "player.h"
+#include "gamerules.h"
+#include "items.h"
+#include "hl1_items.h"
+
+
+#define SF_SUIT_SHORTLOGON 0x0001
+
+#define SUIT_MODEL "models/w_suit.mdl"
+
+extern int gEvilImpulse101;
+
+class CItemSuit : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CItemSuit, CHL1Item );
+
+ void Spawn( void )
+ {
+ Precache( );
+ SetModel( SUIT_MODEL );
+ BaseClass::Spawn( );
+
+ CollisionProp()->UseTriggerBounds( true, 12.0f );
+ }
+ void Precache( void )
+ {
+ PrecacheModel( SUIT_MODEL );
+ }
+ bool MyTouch( CBasePlayer *pPlayer )
+ {
+ if ( pPlayer->IsSuitEquipped() )
+ return false;
+
+ if( !gEvilImpulse101 )
+ {
+ if ( HasSpawnFlags( SF_SUIT_SHORTLOGON ) )
+ UTIL_EmitSoundSuit(pPlayer->edict(), "!HEV_A0"); // short version of suit logon,
+ else
+ UTIL_EmitSoundSuit(pPlayer->edict(), "!HEV_AAx"); // long version of suit logon
+ }
+
+ pPlayer->EquipSuit();
+ return true;
+ }
+};
+
+LINK_ENTITY_TO_CLASS(item_suit, CItemSuit);
+PRECACHE_REGISTER(item_suit);
diff --git a/game/server/hl1/hl1_items.cpp b/game/server/hl1/hl1_items.cpp
new file mode 100644
index 0000000..3024a27
--- /dev/null
+++ b/game/server/hl1/hl1_items.cpp
@@ -0,0 +1,45 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "player.h"
+#include "items.h"
+#include "gamerules.h"
+#include "hl1_items.h"
+
+
+void CHL1Item::Spawn( void )
+{
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE | FSOLID_TRIGGER );
+ CollisionProp()->UseTriggerBounds( true, 24.0f );
+
+ SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+
+ SetTouch( &CItem::ItemTouch );
+
+#ifdef HL1_DLL
+ if ( g_pGameRules->IsMultiplayer() )
+ AddEffects( EF_NOSHADOW );
+#endif
+
+
+}
+
+
+void CHL1Item::Activate( void )
+{
+ BaseClass::Activate();
+
+ if ( UTIL_DropToFloor( this, MASK_SOLID ) == 0 )
+ {
+ Warning( "Item %s fell out of level at %f,%f,%f\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z);
+ UTIL_Remove( this );
+ return;
+ }
+}
diff --git a/game/server/hl1/hl1_items.h b/game/server/hl1/hl1_items.h
new file mode 100644
index 0000000..92685d8
--- /dev/null
+++ b/game/server/hl1/hl1_items.h
@@ -0,0 +1,26 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef HL1_ITEMS_H
+#define HL1_ITEMS_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+#include "items.h"
+
+class CHL1Item : public CItem
+{
+public:
+ DECLARE_CLASS( CHL1Item, CItem );
+
+ void Spawn( void );
+ void Activate( void );
+};
+
+
+#endif // HL1_ITEMS_H
diff --git a/game/server/hl1/hl1_monstermaker.cpp b/game/server/hl1/hl1_monstermaker.cpp
new file mode 100644
index 0000000..c7d37d2
--- /dev/null
+++ b/game/server/hl1/hl1_monstermaker.cpp
@@ -0,0 +1,294 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: An entity that creates NPCs in the game.
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "entityapi.h"
+#include "entityoutput.h"
+#include "ai_basenpc.h"
+#include "hl1_monstermaker.h"
+#include "mapentities.h"
+
+
+BEGIN_DATADESC( CNPCMaker )
+
+ DEFINE_KEYFIELD( m_iMaxNumNPCs, FIELD_INTEGER, "monstercount" ),
+ DEFINE_KEYFIELD( m_iMaxLiveChildren, FIELD_INTEGER, "MaxLiveChildren" ),
+ DEFINE_KEYFIELD( m_flSpawnFrequency, FIELD_FLOAT, "delay" ),
+ DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),
+ DEFINE_KEYFIELD( m_iszNPCClassname, FIELD_STRING, "monstertype" ),
+
+ DEFINE_FIELD( m_cLiveChildren, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flGround, FIELD_FLOAT ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Spawn", InputSpawnNPC ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
+
+ // Outputs
+ DEFINE_OUTPUT( m_OnSpawnNPC, "OnSpawnNPC" ),
+
+ // Function Pointers
+ DEFINE_THINKFUNC( MakerThink ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( monstermaker, CNPCMaker );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn
+//-----------------------------------------------------------------------------
+void CNPCMaker::Spawn( void )
+{
+ SetSolid( SOLID_NONE );
+ m_cLiveChildren = 0;
+ Precache();
+
+ // If I can make an infinite number of NPC, force them to fade
+ if ( m_spawnflags & SF_NPCMAKER_INF_CHILD )
+ {
+ m_spawnflags |= SF_NPCMAKER_FADE;
+ }
+
+ //Start on?
+ if ( m_bDisabled == false )
+ {
+ SetThink ( &CNPCMaker::MakerThink );
+ SetNextThink( gpGlobals->curtime + m_flSpawnFrequency );
+ }
+ else
+ {
+ //wait to be activated.
+ SetThink ( &CBaseEntity::SUB_DoNothing );
+ }
+ m_flGround = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether or not it is OK to make an NPC at this instant.
+//-----------------------------------------------------------------------------
+bool CNPCMaker::CanMakeNPC( void )
+{
+ if ( m_iMaxLiveChildren > 0 && m_cLiveChildren >= m_iMaxLiveChildren )
+ {// not allowed to make a new one yet. Too many live ones out right now.
+ return false;
+ }
+
+ if ( !m_flGround )
+ {
+ // set altitude. Now that I'm activated, any breakables, etc should be out from under me.
+ trace_t tr;
+
+ UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() - Vector ( 0, 0, 2048 ), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+ m_flGround = tr.endpos.z;
+ }
+
+ Vector mins = GetAbsOrigin() - Vector( 34, 34, 0 );
+ Vector maxs = GetAbsOrigin() + Vector( 34, 34, 0 );
+ maxs.z = GetAbsOrigin().z;
+
+ //Only adjust for the ground if we want it
+ if ( ( m_spawnflags & SF_NPCMAKER_NO_DROP ) == false )
+ {
+ mins.z = m_flGround;
+ }
+
+ CBaseEntity *pList[128];
+
+ int count = UTIL_EntitiesInBox( pList, 128, mins, maxs, FL_CLIENT|FL_NPC );
+ if ( count )
+ {
+ //Iterate through the list and check the results
+ for ( int i = 0; i < count; i++ )
+ {
+ //Don't build on top of another entity
+ if ( pList[i] == NULL )
+ continue;
+
+ //If one of the entities is solid, then we can't spawn now
+ if ( ( pList[i]->GetSolidFlags() & FSOLID_NOT_SOLID ) == false )
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: If this had a finite number of children, return true if they've all
+// been created.
+//-----------------------------------------------------------------------------
+bool CNPCMaker::IsDepleted()
+{
+ if ( (m_spawnflags & SF_NPCMAKER_INF_CHILD) || m_iMaxNumNPCs > 0 )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Toggle the spawner's state
+//-----------------------------------------------------------------------------
+void CNPCMaker::Toggle( void )
+{
+ if ( m_bDisabled )
+ {
+ Enable();
+ }
+ else
+ {
+ Disable();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Start the spawner
+//-----------------------------------------------------------------------------
+void CNPCMaker::Enable( void )
+{
+ // can't be enabled once depleted
+ if ( IsDepleted() )
+ return;
+
+ m_bDisabled = false;
+ SetThink ( &CNPCMaker::MakerThink );
+ SetNextThink( gpGlobals->curtime );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Stop the spawner
+//-----------------------------------------------------------------------------
+void CNPCMaker::Disable( void )
+{
+ m_bDisabled = true;
+ SetThink ( NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler that spawns an NPC.
+//-----------------------------------------------------------------------------
+void CNPCMaker::InputSpawnNPC( inputdata_t &inputdata )
+{
+ MakeNPC();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input hander that starts the spawner
+//-----------------------------------------------------------------------------
+void CNPCMaker::InputEnable( inputdata_t &inputdata )
+{
+ Enable();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input hander that stops the spawner
+//-----------------------------------------------------------------------------
+void CNPCMaker::InputDisable( inputdata_t &inputdata )
+{
+ Disable();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input hander that toggles the spawner
+//-----------------------------------------------------------------------------
+void CNPCMaker::InputToggle( inputdata_t &inputdata )
+{
+ Toggle();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache the target NPC
+//-----------------------------------------------------------------------------
+void CNPCMaker::Precache( void )
+{
+ BaseClass::Precache();
+ UTIL_PrecacheOther( STRING( m_iszNPCClassname ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates the NPC.
+//-----------------------------------------------------------------------------
+void CNPCMaker::MakeNPC( void )
+{
+ if (!CanMakeNPC())
+ {
+ return;
+ }
+
+ CBaseEntity *pent = (CBaseEntity*)CreateEntityByName( STRING(m_iszNPCClassname) );
+
+ if ( !pent )
+ {
+ Warning("NULL Ent in NPCMaker!\n" );
+ return;
+ }
+
+ m_OnSpawnNPC.FireOutput( this, this );
+
+ pent->SetLocalOrigin( GetAbsOrigin() );
+ pent->SetLocalAngles( GetAbsAngles() );
+
+ pent->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
+
+ if ( m_spawnflags & SF_NPCMAKER_FADE )
+ {
+ pent->AddSpawnFlags( SF_NPC_FADE_CORPSE );
+ }
+
+
+ DispatchSpawn( pent );
+ pent->SetOwnerEntity( this );
+
+ m_cLiveChildren++;// count this NPC
+
+ if (!(m_spawnflags & SF_NPCMAKER_INF_CHILD))
+ {
+ m_iMaxNumNPCs--;
+
+ if ( IsDepleted() )
+ {
+ // Disable this forever. Don't kill it because it still gets death notices
+ SetThink( NULL );
+ SetUse( NULL );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a new NPC every so often.
+//-----------------------------------------------------------------------------
+void CNPCMaker::MakerThink ( void )
+{
+ SetNextThink( gpGlobals->curtime + m_flSpawnFrequency );
+
+ MakeNPC();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pVictim -
+//-----------------------------------------------------------------------------
+void CNPCMaker::DeathNotice( CBaseEntity *pVictim )
+{
+ // ok, we've gotten the deathnotice from our child, now clear out its owner if we don't want it to fade.
+ m_cLiveChildren--;
+}
diff --git a/game/server/hl1/hl1_monstermaker.h b/game/server/hl1/hl1_monstermaker.h
new file mode 100644
index 0000000..2646020
--- /dev/null
+++ b/game/server/hl1/hl1_monstermaker.h
@@ -0,0 +1,71 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#ifndef MONSTERMAKER_H
+#define MONSTERMAKER_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "cbase.h"
+
+
+//-----------------------------------------------------------------------------
+// Spawnflags
+//-----------------------------------------------------------------------------
+#define SF_NPCMAKER_START_ON 1 // start active ( if has targetname )
+#define SF_NPCMAKER_NPCCLIP 8 // Children are blocked by NPCclip
+#define SF_NPCMAKER_FADE 16 // Children's corpses fade
+#define SF_NPCMAKER_INF_CHILD 32 // Infinite number of children
+#define SF_NPCMAKER_NO_DROP 64 // Do not adjust for the ground's position when checking for spawn
+
+
+class CNPCMaker : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CNPCMaker, CBaseEntity );
+
+ CNPCMaker(void) {}
+
+ void Spawn( void );
+ void Precache( void );
+
+ void MakerThink( void );
+ bool CanMakeNPC( void );
+
+ void DeathNotice( CBaseEntity *pChild );// NPC maker children use this to tell the NPC maker that they have died.
+ void MakeNPC( void );
+
+ // Input handlers
+ void InputSpawnNPC( inputdata_t &inputdata );
+ void InputEnable( inputdata_t &inputdata );
+ void InputDisable( inputdata_t &inputdata );
+ void InputToggle( inputdata_t &inputdata );
+
+ // State changers
+ void Toggle( void );
+ void Enable( void );
+ void Disable( void );
+
+ bool IsDepleted();
+
+ DECLARE_DATADESC();
+
+ int m_iMaxNumNPCs; // max number of NPCs this ent can create
+ float m_flSpawnFrequency; // delay (in secs) between spawns
+ int m_iMaxLiveChildren; // max number of NPCs that this maker may have out at one time.
+ string_t m_iszNPCClassname; // classname of the NPC(s) that will be created.
+
+ COutputEvent m_OnSpawnNPC;
+
+ int m_cLiveChildren;// how many NPCs made by this NPC maker that are currently alive
+
+ float m_flGround; // z coord of the ground under me, used to make sure no NPCs are under the maker when it drops a new child
+ bool m_bDisabled;
+};
+
+
+#endif // MONSTERMAKER_H
diff --git a/game/server/hl1/hl1_npc_aflock.cpp b/game/server/hl1/hl1_npc_aflock.cpp
new file mode 100644
index 0000000..01b1c76
--- /dev/null
+++ b/game/server/hl1/hl1_npc_aflock.cpp
@@ -0,0 +1,858 @@
+//========= 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_route.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"
+
+#define AFLOCK_MAX_RECRUIT_RADIUS 1024
+#define AFLOCK_FLY_SPEED 125
+#define AFLOCK_TURN_RATE 75
+#define AFLOCK_ACCELERATE 10
+#define AFLOCK_CHECK_DIST 192
+#define AFLOCK_TOO_CLOSE 100
+#define AFLOCK_TOO_FAR 256
+
+//=========================================================
+//=========================================================
+class CNPC_FlockingFlyerFlock : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_FlockingFlyerFlock, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ void SpawnFlock( void );
+
+ // Sounds are shared by the flock
+ static void PrecacheFlockSounds( void );
+
+ DECLARE_DATADESC();
+
+ int m_cFlockSize;
+ float m_flFlockRadius;
+};
+
+BEGIN_DATADESC( CNPC_FlockingFlyerFlock )
+ DEFINE_FIELD( m_cFlockSize, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flFlockRadius, FIELD_FLOAT ),
+END_DATADESC()
+
+class CNPC_FlockingFlyer : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_FlockingFlyer, CHL1BaseNPC );
+public:
+ void Spawn( void );
+ void Precache( void );
+ void SpawnCommonCode( void );
+ void IdleThink( void );
+ void BoidAdvanceFrame( void );
+ void Start( void );
+ bool FPathBlocked( void );
+ void FlockLeaderThink( void );
+ void SpreadFlock( void );
+ void SpreadFlock2( void );
+ void MakeSound( void );
+ void FlockFollowerThink( void );
+ void Event_Killed( const CTakeDamageInfo &info );
+ void FallHack( void );
+ //void Poop ( void ); Adrian - wtf?!
+
+
+
+ int IsLeader( void ) { return m_pSquadLeader == this; }
+ int InSquad( void ) { return m_pSquadLeader != NULL; }
+ int SquadCount( void );
+ void SquadRemove( CNPC_FlockingFlyer *pRemove );
+ void SquadUnlink( void );
+ void SquadAdd( CNPC_FlockingFlyer *pAdd );
+ void SquadDisband( void );
+
+ CNPC_FlockingFlyer *m_pSquadLeader;
+ CNPC_FlockingFlyer *m_pSquadNext;
+ bool m_fTurning;// is this boid turning?
+ bool m_fCourseAdjust;// followers set this flag TRUE to override flocking while they avoid something
+ bool m_fPathBlocked;// TRUE if there is an obstacle ahead
+ Vector m_vecReferencePoint;// last place we saw leader
+ Vector m_vecAdjustedVelocity;// adjusted velocity (used when fCourseAdjust is TRUE)
+ float m_flGoalSpeed;
+ float m_flLastBlockedTime;
+ float m_flFakeBlockedTime;
+ float m_flAlertTime;
+ float m_flFlockNextSoundTime;
+ float m_flTempVar;
+
+ DECLARE_DATADESC();
+};
+
+BEGIN_DATADESC( CNPC_FlockingFlyer )
+ DEFINE_FIELD( m_pSquadLeader, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_pSquadNext, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_fTurning, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fCourseAdjust, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fPathBlocked, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vecReferencePoint, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecAdjustedVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flGoalSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLastBlockedTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flFakeBlockedTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flAlertTime, FIELD_TIME ),
+ DEFINE_THINKFUNC( IdleThink ),
+ DEFINE_THINKFUNC( Start ),
+ DEFINE_THINKFUNC( FlockLeaderThink ),
+ DEFINE_THINKFUNC( FlockFollowerThink ),
+ DEFINE_THINKFUNC( FallHack ),
+
+ DEFINE_FIELD( m_flFlockNextSoundTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTempVar, FIELD_FLOAT ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( monster_flyer, CNPC_FlockingFlyer );
+LINK_ENTITY_TO_CLASS( monster_flyer_flock, CNPC_FlockingFlyerFlock );
+
+bool CNPC_FlockingFlyerFlock::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "iFlockSize" ) )
+ {
+ m_cFlockSize = atoi( szValue );
+ return true;
+ }
+ else if ( FStrEq( szKeyName, "flFlockRadius" ) )
+ {
+ m_flFlockRadius = atof( szValue );
+ return true;
+ }
+ else
+ BaseClass::KeyValue( szKeyName, szValue );
+
+ return false;
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyerFlock::Spawn( void )
+{
+ Precache( );
+
+ SetRenderColor( 255, 255, 255, 255 );
+ SpawnFlock();
+
+
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyerFlock::Precache( void )
+{
+ //PRECACHE_MODEL("models/aflock.mdl");
+ PrecacheModel("models/boid.mdl");
+
+ PrecacheFlockSounds();
+}
+
+void CNPC_FlockingFlyerFlock::SpawnFlock( void )
+{
+ float R = m_flFlockRadius;
+ int iCount;
+ Vector vecSpot;
+ CNPC_FlockingFlyer *pBoid, *pLeader;
+
+ pLeader = pBoid = NULL;
+
+ for ( iCount = 0 ; iCount < m_cFlockSize ; iCount++ )
+ {
+ pBoid = CREATE_ENTITY( CNPC_FlockingFlyer, "monster_flyer" );
+
+ if ( !pLeader )
+ {
+ // make this guy the leader.
+ pLeader = pBoid;
+
+ pLeader->m_pSquadLeader = pLeader;
+ pLeader->m_pSquadNext = NULL;
+ }
+
+ vecSpot.x = random->RandomFloat( -R, R );
+ vecSpot.y = random->RandomFloat( -R, R );
+ vecSpot.z = random->RandomFloat( 0, 16 );
+ vecSpot = GetAbsOrigin() + vecSpot;
+
+ UTIL_SetOrigin( pBoid, vecSpot);
+ pBoid->SetMoveType( MOVETYPE_FLY );
+ pBoid->SpawnCommonCode();
+ pBoid->SetGroundEntity( NULL );
+ pBoid->SetAbsVelocity( Vector ( 0, 0, 0 ) );
+ pBoid->SetAbsAngles( GetAbsAngles() );
+
+ pBoid->SetCycle( 0 );
+ pBoid->SetThink( &CNPC_FlockingFlyer::IdleThink );
+ pBoid->SetNextThink( gpGlobals->curtime + 0.2 );
+
+ if ( pBoid != pLeader )
+ {
+ pLeader->SquadAdd( pBoid );
+ }
+ }
+}
+
+
+void CNPC_FlockingFlyerFlock::PrecacheFlockSounds( void )
+{
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyer::Spawn( )
+{
+ Precache( );
+ SpawnCommonCode();
+
+ SetCycle( 0 );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ SetThink( &CNPC_FlockingFlyer::IdleThink );
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyer::SpawnCommonCode( )
+{
+ m_lifeState = LIFE_ALIVE;
+ SetClassname( "monster_flyer" );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_FLY );
+ m_takedamage = DAMAGE_NO;
+ m_iHealth = 1;
+
+ m_fPathBlocked = FALSE;// obstacles will be detected
+ m_flFieldOfView = 0.2;
+ m_flTempVar = 0;
+
+ //SET_MODEL(ENT(pev), "models/aflock.mdl");
+ SetModel( "models/boid.mdl" );
+
+// UTIL_SetSize(this, Vector(0,0,0), Vector(0,0,0));
+ UTIL_SetSize(this, Vector(-5,-5,0), Vector(5,5,2));
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyer::Precache( )
+{
+ //PRECACHE_MODEL("models/aflock.mdl");
+ PrecacheModel("models/boid.mdl");
+ CNPC_FlockingFlyerFlock::PrecacheFlockSounds();
+
+ PrecacheScriptSound( "FlockingFlyer.Alert" );
+ PrecacheScriptSound( "FlockingFlyer.Idle" );
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyer::IdleThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.2 );
+
+ // see if there's a client in the same pvs as the monster
+ if ( !FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
+ {
+ SetThink( &CNPC_FlockingFlyer::Start );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+}
+
+//=========================================================
+//
+// SquadUnlink(), Unlink the squad pointers.
+//
+//=========================================================
+void CNPC_FlockingFlyer::SquadUnlink( void )
+{
+ m_pSquadLeader = NULL;
+ m_pSquadNext = NULL;
+}
+
+//=========================================================
+//
+// SquadAdd(), add pAdd to my squad
+//
+//=========================================================
+void CNPC_FlockingFlyer::SquadAdd( CNPC_FlockingFlyer *pAdd )
+{
+ ASSERT( pAdd!=NULL );
+ ASSERT( !pAdd->InSquad() );
+ ASSERT( this->IsLeader() );
+
+ pAdd->m_pSquadNext = m_pSquadNext;
+ m_pSquadNext = pAdd;
+ pAdd->m_pSquadLeader = this;
+}
+//=========================================================
+//
+// SquadRemove(), remove pRemove from my squad.
+// If I am pRemove, promote m_pSquadNext to leader
+//
+//=========================================================
+void CNPC_FlockingFlyer::SquadRemove( CNPC_FlockingFlyer *pRemove )
+{
+ ASSERT( pRemove!=NULL );
+ ASSERT( this->IsLeader() );
+ ASSERT( pRemove->m_pSquadLeader == this );
+
+ if ( SquadCount() > 2 )
+ {
+ // Removing the leader, promote m_pSquadNext to leader
+ if ( pRemove == this )
+ {
+ CNPC_FlockingFlyer *pLeader = m_pSquadNext;
+
+ // copy the enemy LKP to the new leader
+
+ // if ( GetEnemy() )
+ // pLeader->m_vecEnemyLKP = m_vecEnemyLKP;
+
+ if ( pLeader )
+ {
+ CNPC_FlockingFlyer *pList = pLeader;
+
+ while ( pList )
+ {
+ pList->m_pSquadLeader = pLeader;
+ pList = pList->m_pSquadNext;
+ }
+
+ }
+ SquadUnlink();
+ }
+ else // removing a node
+ {
+ CNPC_FlockingFlyer *pList = this;
+
+ // Find the node before pRemove
+ while ( pList->m_pSquadNext != pRemove )
+ {
+ // assert to test valid list construction
+ ASSERT( pList->m_pSquadNext != NULL );
+ pList = pList->m_pSquadNext;
+ }
+ // List validity
+ ASSERT( pList->m_pSquadNext == pRemove );
+
+ // Relink without pRemove
+ pList->m_pSquadNext = pRemove->m_pSquadNext;
+
+ // Unlink pRemove
+ pRemove->SquadUnlink();
+ }
+ }
+ else
+ SquadDisband();
+}
+//=========================================================
+//
+// SquadCount(), return the number of members of this squad
+// callable from leaders & followers
+//
+//=========================================================
+int CNPC_FlockingFlyer::SquadCount( void )
+{
+ CNPC_FlockingFlyer *pList = m_pSquadLeader;
+ int squadCount = 0;
+ while ( pList )
+ {
+ squadCount++;
+ pList = pList->m_pSquadNext;
+ }
+
+ return squadCount;
+}
+
+//=========================================================
+//
+// SquadDisband(), Unlink all squad members
+//
+//=========================================================
+void CNPC_FlockingFlyer::SquadDisband( void )
+{
+ CNPC_FlockingFlyer *pList = m_pSquadLeader;
+ CNPC_FlockingFlyer *pNext;
+
+ while ( pList )
+ {
+ pNext = pList->m_pSquadNext;
+ pList->SquadUnlink();
+ pList = pNext;
+ }
+}
+
+//=========================================================
+// Start - player enters the pvs, so get things going.
+//=========================================================
+void CNPC_FlockingFlyer::Start( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if ( IsLeader() )
+ {
+ SetThink( &CNPC_FlockingFlyer::FlockLeaderThink );
+ }
+ else
+ {
+ SetThink( &CNPC_FlockingFlyer::FlockFollowerThink );
+ }
+
+ SetActivity ( ACT_FLY );
+ ResetSequenceInfo( );
+ BoidAdvanceFrame( );
+
+ m_flSpeed = AFLOCK_FLY_SPEED;// no delay!
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyer::BoidAdvanceFrame ( void )
+{
+ float flapspeed = ( m_flSpeed - m_flTempVar ) / AFLOCK_ACCELERATE;
+ m_flTempVar = m_flTempVar * .8 + m_flSpeed * .2;
+
+ if (flapspeed < 0) flapspeed = -flapspeed;
+ if (flapspeed < 0.25) flapspeed = 0.25;
+ if (flapspeed > 1.9) flapspeed = 1.9;
+
+ m_flPlaybackRate = flapspeed;
+
+ QAngle angVel = GetLocalAngularVelocity();
+
+ // lean
+ angVel.x = - GetAbsAngles().x + flapspeed * 5;
+
+ // bank
+ angVel.z = - GetAbsAngles().z + angVel.y;
+
+ SetLocalAngularVelocity( angVel );
+
+ // pev->framerate = flapspeed;
+ StudioFrameAdvance();
+}
+
+//=========================================================
+// Leader boids use this think every tenth
+//=========================================================
+void CNPC_FlockingFlyer::FlockLeaderThink( void )
+{
+ trace_t tr;
+ Vector vecDist;// used for general measurements
+ Vector vecDir;// used for general measurements
+ float flLeftSide;
+ float flRightSide;
+ Vector vForward, vRight, vUp;
+
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ AngleVectors ( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ // is the way ahead clear?
+ if ( !FPathBlocked () )
+ {
+ // if the boid is turning, stop the trend.
+ if ( m_fTurning )
+ {
+ m_fTurning = FALSE;
+
+ QAngle angVel = GetLocalAngularVelocity();
+ angVel.y = 0;
+ SetLocalAngularVelocity( angVel );
+ }
+
+ m_fPathBlocked = FALSE;
+
+ if ( m_flSpeed <= AFLOCK_FLY_SPEED )
+ m_flSpeed += 5;
+
+ SetAbsVelocity( vForward * m_flSpeed );
+
+ BoidAdvanceFrame( );
+
+ return;
+ }
+
+ // IF we get this far in the function, the leader's path is blocked!
+ m_fPathBlocked = TRUE;
+
+ if ( !m_fTurning)// something in the way and boid is not already turning to avoid
+ {
+ // measure clearance on left and right to pick the best dir to turn
+ UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vRight * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ vecDist = (tr.endpos - GetAbsOrigin());
+ flRightSide = vecDist.Length();
+
+ UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() - vRight * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ vecDist = (tr.endpos - GetAbsOrigin());
+ flLeftSide = vecDist.Length();
+
+ // turn right if more clearance on right side
+ if ( flRightSide > flLeftSide )
+ {
+ QAngle angVel = GetLocalAngularVelocity();
+ angVel.y = -AFLOCK_TURN_RATE;
+ SetLocalAngularVelocity( angVel );
+
+ m_fTurning = TRUE;
+ }
+ // default to left turn :)
+ else if ( flLeftSide > flRightSide )
+ {
+ QAngle angVel = GetLocalAngularVelocity();
+ angVel.y = AFLOCK_TURN_RATE;
+ SetLocalAngularVelocity( angVel );
+
+ m_fTurning = TRUE;
+ }
+ else
+ {
+ // equidistant. Pick randomly between left and right.
+ m_fTurning = TRUE;
+
+ QAngle angVel = GetLocalAngularVelocity();
+
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ angVel.y = AFLOCK_TURN_RATE;
+ }
+ else
+ {
+ angVel.y = -AFLOCK_TURN_RATE;
+ }
+
+ SetLocalAngularVelocity( angVel );
+ }
+ }
+
+ SpreadFlock( );
+
+ SetAbsVelocity( vForward * m_flSpeed );
+
+ // check and make sure we aren't about to plow into the ground, don't let it happen
+ UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() - vUp * 16, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ if (tr.fraction != 1.0 && GetAbsVelocity().z < 0 )
+ {
+ Vector vecVel = GetAbsVelocity();
+ vecVel.z = 0;
+ SetAbsVelocity( vecVel );
+ }
+
+ // maybe it did, though.
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ UTIL_SetOrigin( this, GetAbsOrigin() + Vector ( 0 , 0 , 1 ) );
+ Vector vecVel = GetAbsVelocity();
+ vecVel.z = 0;
+ SetAbsVelocity( vecVel );
+ }
+
+ if ( m_flFlockNextSoundTime < gpGlobals->curtime )
+ {
+// MakeSound();
+ m_flFlockNextSoundTime = gpGlobals->curtime + random->RandomFloat( 1, 3 );
+ }
+
+ BoidAdvanceFrame( );
+
+ return;
+}
+
+//=========================================================
+// FBoidPathBlocked - returns TRUE if there is an obstacle ahead
+//=========================================================
+bool CNPC_FlockingFlyer::FPathBlocked( void )
+{
+ trace_t tr;
+ Vector vecDist;// used for general measurements
+ Vector vecDir;// used for general measurements
+ bool fBlocked;
+ Vector vForward, vRight, vUp;
+
+ if ( m_flFakeBlockedTime > gpGlobals->curtime )
+ {
+ m_flLastBlockedTime = gpGlobals->curtime;
+ return TRUE;
+ }
+
+ // use VELOCITY, not angles, not all boids point the direction they are flying
+ //vecDir = UTIL_VecToAngles( pevBoid->velocity );
+ AngleVectors ( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ fBlocked = FALSE;// assume the way ahead is clear
+
+ // check for obstacle ahead
+ UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vForward * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+
+ if (tr.fraction != 1.0)
+ {
+ m_flLastBlockedTime = gpGlobals->curtime;
+ fBlocked = TRUE;
+ }
+
+ // extra wide checks
+ UTIL_TraceLine(GetAbsOrigin() + vRight * 12, GetAbsOrigin() + vRight * 12 + vForward * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+
+ if (tr.fraction != 1.0)
+ {
+ m_flLastBlockedTime = gpGlobals->curtime;
+ fBlocked = TRUE;
+ }
+
+ UTIL_TraceLine(GetAbsOrigin() - vRight * 12, GetAbsOrigin() - vRight * 12 + vForward * AFLOCK_CHECK_DIST, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+
+ if (tr.fraction != 1.0)
+ {
+ m_flLastBlockedTime = gpGlobals->curtime;
+ fBlocked = TRUE;
+ }
+
+ if ( !fBlocked && gpGlobals->curtime - m_flLastBlockedTime > 6 )
+ {
+ // not blocked, and it's been a few seconds since we've actually been blocked.
+ m_flFakeBlockedTime = gpGlobals->curtime + random->RandomInt(1, 3);
+ }
+
+ return fBlocked;
+}
+
+//=========================================================
+// Searches for boids that are too close and pushes them away
+//=========================================================
+void CNPC_FlockingFlyer::SpreadFlock( )
+{
+ Vector vecDir;
+ float flSpeed;// holds vector magnitude while we fiddle with the direction
+
+ CNPC_FlockingFlyer *pList = m_pSquadLeader;
+ while ( pList )
+ {
+ if ( pList != this && ( GetAbsOrigin() - pList->GetAbsOrigin() ).Length() <= AFLOCK_TOO_CLOSE )
+ {
+ // push the other away
+ vecDir = ( pList->GetAbsOrigin() - GetAbsOrigin() );
+ VectorNormalize( vecDir );
+
+ // store the magnitude of the other boid's velocity, and normalize it so we
+ // can average in a course that points away from the leader.
+ flSpeed = pList->GetAbsVelocity().Length();
+
+ Vector vecVel = pList->GetAbsVelocity();
+ VectorNormalize( vecVel );
+ pList->SetAbsVelocity( ( vecVel + vecDir ) * 0.5 * flSpeed );
+ }
+
+ pList = pList->m_pSquadNext;
+ }
+}
+
+//=========================================================
+// Alters the caller's course if he's too close to others
+//
+// This function should **ONLY** be called when Caller's velocity is normalized!!
+//=========================================================
+void CNPC_FlockingFlyer::SpreadFlock2 ( )
+{
+ Vector vecDir;
+
+ CNPC_FlockingFlyer *pList = m_pSquadLeader;
+
+ while ( pList )
+ {
+ if ( pList != this && ( GetAbsOrigin() - pList->GetAbsOrigin() ).Length() <= AFLOCK_TOO_CLOSE )
+ {
+ vecDir = ( GetAbsOrigin() - pList->GetAbsOrigin() );
+ VectorNormalize( vecDir );
+
+ SetAbsVelocity( ( GetAbsVelocity() + vecDir ) );
+ }
+
+ pList = pList->m_pSquadNext;
+ }
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyer::MakeSound( void )
+{
+ if ( m_flAlertTime > gpGlobals->curtime )
+ {
+ CPASAttenuationFilter filter1( this );
+
+ // make agitated sounds
+ EmitSound( filter1, entindex(), "FlockingFlyer.Alert" );
+ return;
+ }
+
+ // make normal sound
+ CPASAttenuationFilter filter2( this );
+
+ EmitSound( filter2, entindex(), "FlockingFlyer.Idle" );
+}
+
+//=========================================================
+// follower boids execute this code when flocking
+//=========================================================
+void CNPC_FlockingFlyer::FlockFollowerThink( void )
+{
+ Vector vecDist;
+ Vector vecDir;
+ Vector vecDirToLeader;
+ float flDistToLeader;
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if ( IsLeader() || !InSquad() )
+ {
+ // the leader has been killed and this flyer suddenly finds himself the leader.
+ SetThink ( &CNPC_FlockingFlyer::FlockLeaderThink );
+ return;
+ }
+
+ vecDirToLeader = ( m_pSquadLeader->GetAbsOrigin() - GetAbsOrigin() );
+ flDistToLeader = vecDirToLeader.Length();
+
+ // match heading with leader
+ SetAbsAngles( m_pSquadLeader->GetAbsAngles() );
+
+ //
+ // We can see the leader, so try to catch up to it
+ //
+ if ( FInViewCone ( m_pSquadLeader ) )
+ {
+ // if we're too far away, speed up
+ if ( flDistToLeader > AFLOCK_TOO_FAR )
+ {
+ m_flGoalSpeed = m_pSquadLeader->GetAbsVelocity().Length() * 1.5;
+ }
+
+ // if we're too close, slow down
+ else if ( flDistToLeader < AFLOCK_TOO_CLOSE )
+ {
+ m_flGoalSpeed = m_pSquadLeader->GetAbsVelocity().Length() * 0.5;
+ }
+ }
+ else
+ {
+ // wait up! the leader isn't out in front, so we slow down to let him pass
+ m_flGoalSpeed = m_pSquadLeader->GetAbsVelocity().Length() * 0.5;
+ }
+
+ SpreadFlock2();
+
+ Vector vecVel = GetAbsVelocity();
+ m_flSpeed = vecVel.Length();
+ VectorNormalize( vecVel );
+
+ // if we are too far from leader, average a vector towards it into our current velocity
+ if ( flDistToLeader > AFLOCK_TOO_FAR )
+ {
+ VectorNormalize( vecDirToLeader );
+ vecVel = (vecVel + vecDirToLeader) * 0.5;
+ }
+
+ // clamp speeds and handle acceleration
+ if ( m_flGoalSpeed > AFLOCK_FLY_SPEED * 2 )
+ {
+ m_flGoalSpeed = AFLOCK_FLY_SPEED * 2;
+ }
+
+ if ( m_flSpeed < m_flGoalSpeed )
+ {
+ m_flSpeed += AFLOCK_ACCELERATE;
+ }
+ else if ( m_flSpeed > m_flGoalSpeed )
+ {
+ m_flSpeed -= AFLOCK_ACCELERATE;
+ }
+
+ SetAbsVelocity( vecVel * m_flSpeed );
+
+ BoidAdvanceFrame( );
+}
+
+//=========================================================
+//=========================================================
+void CNPC_FlockingFlyer::Event_Killed( const CTakeDamageInfo &info )
+{
+ CNPC_FlockingFlyer *pSquad;
+
+ pSquad = (CNPC_FlockingFlyer *)m_pSquadLeader;
+
+ while ( pSquad )
+ {
+ pSquad->m_flAlertTime = gpGlobals->curtime + 15;
+ pSquad = (CNPC_FlockingFlyer *)pSquad->m_pSquadNext;
+ }
+
+ if ( m_pSquadLeader )
+ {
+ m_pSquadLeader->SquadRemove( this );
+ }
+
+ m_lifeState = LIFE_DEAD;
+
+ m_flPlaybackRate = 0;
+ IncrementInterpolationFrame();
+
+ UTIL_SetSize( this, Vector(0,0,0), Vector(0,0,0) );
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+
+ SetThink ( &CNPC_FlockingFlyer::FallHack );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+void CNPC_FlockingFlyer::FallHack( void )
+{
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ CBaseEntity *groundentity = GetContainingEntity( GetGroundEntity()->edict() );
+
+ if ( !FClassnameIs ( groundentity, "worldspawn" ) )
+ {
+ SetGroundEntity( NULL );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+ else
+ {
+ SetAbsVelocity( Vector( 0, 0, 0 ) );
+ SetThink( NULL );
+ }
+ }
+}
diff --git a/game/server/hl1/hl1_npc_agrunt.cpp b/game/server/hl1/hl1_npc_agrunt.cpp
new file mode 100644
index 0000000..484448a
--- /dev/null
+++ b/game/server/hl1/hl1_npc_agrunt.cpp
@@ -0,0 +1,1127 @@
+//========= 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_squadslot.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 "te.h"
+#include "hl1_ai_basenpc.h"
+
+ConVar sk_agrunt_health( "sk_agrunt_health", "0" );
+ConVar sk_agrunt_dmg_punch( "sk_agrunt_dmg_punch", "0" );
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define AGRUNT_AE_HORNET1 ( 1 )
+#define AGRUNT_AE_HORNET2 ( 2 )
+#define AGRUNT_AE_HORNET3 ( 3 )
+#define AGRUNT_AE_HORNET4 ( 4 )
+#define AGRUNT_AE_HORNET5 ( 5 )
+// some events are set up in the QC file that aren't recognized by the code yet.
+#define AGRUNT_AE_PUNCH ( 6 )
+#define AGRUNT_AE_BITE ( 7 )
+
+#define AGRUNT_AE_LEFT_FOOT ( 10 )
+#define AGRUNT_AE_RIGHT_FOOT ( 11 )
+
+#define AGRUNT_AE_LEFT_PUNCH ( 12 )
+#define AGRUNT_AE_RIGHT_PUNCH ( 13 )
+
+#define AGRUNT_MELEE_DIST 100
+
+int iAgruntMuzzleFlash;
+int ACT_THREAT_DISPLAY;
+
+// -----------------------------------------------
+// > Squad slots
+// -----------------------------------------------
+enum AGruntSquadSlot_T
+{
+ AGRUNT_SQUAD_SLOT_HORNET1 = LAST_SHARED_SQUADSLOT,
+ AGRUNT_SQUAD_SLOT_HORNET2,
+ AGRUNT_SQUAD_SLOT_CHASE,
+};
+
+enum
+{
+ SCHED_AGRUNT_FAIL = LAST_SHARED_SCHEDULE,
+ SCHED_AGRUNT_COMBAT_FAIL,
+ SCHED_AGRUNT_STANDOFF,
+ SCHED_AGRUNT_SUPPRESS_HORNET,
+ SCHED_AGRUNT_RANGE_ATTACK,
+ SCHED_AGRUNT_HIDDEN_RANGE_ATTACK,
+ SCHED_AGRUNT_TAKE_COVER_FROM_ENEMY,
+ SCHED_AGRUNT_VICTORY_DANCE,
+ SCHED_AGRUNT_THREAT_DISPLAY,
+};
+
+//=========================================================
+// monster-specific tasks
+//=========================================================
+enum
+{
+ TASK_AGRUNT_SETUP_HIDE_ATTACK = LAST_SHARED_TASK,
+ TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE,
+ TASK_AGRUNT_RANGE_ATTACK1_NOTURN,
+};
+
+
+class CNPC_AlienGrunt : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_AlienGrunt, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+
+ float MaxYawSpeed( void );
+ Class_T Classify ( void ){ return CLASS_ALIEN_MILITARY; }
+ int GetSoundInterests ( void );
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ void AlertSound( void );
+ void DeathSound( const CTakeDamageInfo &info );
+ void PainSound( const CTakeDamageInfo &info );
+ void AttackSound( void );
+
+ bool ShouldSpeak( void );
+ void PrescheduleThink ( void );
+
+ bool FCanCheckAttacks ( void );
+
+ int MeleeAttack1Conditions ( float flDot, float flDist );
+ int RangeAttack1Conditions ( float flDot, float flDist );
+
+ void StopTalking ( void );
+
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+
+ int TranslateSchedule( int scheduleType ); //GetScheduleOfType
+ int SelectSchedule( void ); // GetSchedule
+
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ int IRelationPriority( CBaseEntity *pTarget );
+/*
+ int IRelationship( CBaseEntity *pTarget );
+*/
+public:
+ DECLARE_DATADESC();
+ DEFINE_CUSTOM_AI;
+
+ bool m_fCanHornetAttack;
+ float m_flNextHornetAttackCheck;
+
+ float m_flNextPainTime;
+
+ // three hacky fields for speech stuff. These don't really need to be saved.
+ float m_flNextSpeakTime;
+ float m_flNextWordTime;
+ float m_flDamageTime;
+};
+
+LINK_ENTITY_TO_CLASS( monster_alien_grunt, CNPC_AlienGrunt );
+
+BEGIN_DATADESC( CNPC_AlienGrunt )
+ DEFINE_FIELD( m_fCanHornetAttack, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flNextHornetAttackCheck, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextPainTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextSpeakTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextWordTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flDamageTime, FIELD_TIME ),
+END_DATADESC()
+
+int CNPC_AlienGrunt::IRelationPriority( CBaseEntity *pTarget )
+{
+ //I hate grunts more than anything.
+ if ( pTarget->Classify() == CLASS_HUMAN_MILITARY )
+ {
+ if ( FClassnameIs( pTarget, "monster_human_grunt" ) )
+ {
+ return ( BaseClass::IRelationPriority ( pTarget ) + 1 );
+ }
+ }
+
+ return BaseClass::IRelationPriority( pTarget );
+}
+
+void CNPC_AlienGrunt::Spawn()
+{
+ Precache();
+
+ SetModel( "models/agrunt.mdl");
+ UTIL_SetSize( this, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ Vector vecSurroundingMins( -32, -32, 0 );
+ Vector vecSurroundingMaxs( 32, 32, 85 );
+ CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );
+
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ ClearEffects();
+ m_iHealth = sk_agrunt_health.GetFloat();
+ m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd ( bits_CAP_SQUAD | bits_CAP_MOVE_GROUND );
+
+ CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK1 );
+
+ // Innate range attack for kicking
+ CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 );
+
+ m_HackedGunPos = Vector( 24, 64, 48 );
+
+ m_flNextSpeakTime = m_flNextWordTime = gpGlobals->curtime + 10 + random->RandomInt( 0, 10 );
+
+ SetHullType(HULL_WIDE_HUMAN);
+ SetHullSizeNormal();
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ NPCInit();
+
+ BaseClass::Spawn();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_AlienGrunt::Precache()
+{
+ PrecacheModel("models/agrunt.mdl");
+
+ iAgruntMuzzleFlash = PrecacheModel( "sprites/muz4.vmt" );
+
+ UTIL_PrecacheOther( "hornet" );
+
+ PrecacheScriptSound( "Weapon_Hornetgun.Single" );
+ PrecacheScriptSound( "AlienGrunt.LeftFoot" );
+ PrecacheScriptSound( "AlienGrunt.RightFoot" );
+ PrecacheScriptSound( "AlienGrunt.AttackHit" );
+ PrecacheScriptSound( "AlienGrunt.AttackMiss" );
+ PrecacheScriptSound( "AlienGrunt.Die" );
+ PrecacheScriptSound( "AlienGrunt.Alert" );
+ PrecacheScriptSound( "AlienGrunt.Attack" );
+ PrecacheScriptSound( "AlienGrunt.Pain" );
+ PrecacheScriptSound( "AlienGrunt.Idle" );
+}
+
+float CNPC_AlienGrunt::MaxYawSpeed( void )
+{
+ float ys;
+
+ switch ( GetActivity() )
+ {
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ ys = 110;
+ break;
+ default:
+ ys = 100;
+ }
+
+ return ys;
+}
+
+int CNPC_AlienGrunt::GetSoundInterests ( void )
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_PLAYER |
+ SOUND_DANGER;
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//
+// Returns number of events handled, 0 if none.
+//=========================================================
+void CNPC_AlienGrunt::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case AGRUNT_AE_HORNET1:
+ case AGRUNT_AE_HORNET2:
+ case AGRUNT_AE_HORNET3:
+ case AGRUNT_AE_HORNET4:
+ case AGRUNT_AE_HORNET5:
+ {
+ // m_vecEnemyLKP should be center of enemy body
+ Vector vecArmPos;
+ QAngle angArmDir;
+ Vector vecDirToEnemy;
+ QAngle angDir;
+
+ if (HasCondition( COND_SEE_ENEMY) && GetEnemy())
+ {
+ Vector vecEnemyLKP = GetEnemy()->GetAbsOrigin();
+
+ vecDirToEnemy = ( ( vecEnemyLKP ) - GetAbsOrigin() );
+ VectorAngles( vecDirToEnemy, angDir );
+ VectorNormalize( vecDirToEnemy );
+ }
+ else
+ {
+ angDir = GetAbsAngles();
+ angDir.x = -angDir.x;
+
+ Vector vForward;
+ AngleVectors( angDir, &vForward );
+ vecDirToEnemy = vForward;
+ }
+
+ DoMuzzleFlash();
+
+ // make angles +-180
+ if (angDir.x > 180)
+ {
+ angDir.x = angDir.x - 360;
+ }
+
+ // SetBlending( 0, angDir.x );
+ GetAttachment( "0", vecArmPos, angArmDir );
+
+ vecArmPos = vecArmPos + vecDirToEnemy * 32;
+
+ CPVSFilter filter( GetAbsOrigin() );
+ te->Sprite( filter, 0.0,
+ &vecArmPos, iAgruntMuzzleFlash, random->RandomFloat( 0.4, 0.8 ), 128 );
+
+ CBaseEntity *pHornet = CBaseEntity::Create( "hornet", vecArmPos, QAngle( 0, 0, 0 ), this );
+
+ Vector vForward;
+ AngleVectors( angDir, &vForward );
+
+ pHornet->SetAbsVelocity( vForward * 300 );
+ pHornet->SetOwnerEntity( this );
+
+ EmitSound( "Weapon_Hornetgun.Single" );
+
+ CHL1BaseNPC *pHornetMonster = (CHL1BaseNPC *)pHornet->MyNPCPointer();
+
+ if ( pHornetMonster )
+ {
+ pHornetMonster->SetEnemy( GetEnemy() );
+ }
+ }
+ break;
+
+ case AGRUNT_AE_LEFT_FOOT:
+ // left foot
+ {
+ CPASAttenuationFilter filter2( this );
+ EmitSound( filter2, entindex(), "AlienGrunt.LeftFoot" );
+ }
+ break;
+ case AGRUNT_AE_RIGHT_FOOT:
+ // right foot
+ {
+ CPASAttenuationFilter filter3( this );
+ EmitSound( filter3, entindex(), "AlienGrunt.RightFoot" );
+ }
+ break;
+
+ case AGRUNT_AE_LEFT_PUNCH:
+ {
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, vecMins, vecMaxs, sk_agrunt_dmg_punch.GetFloat(), DMG_CLUB );
+ CPASAttenuationFilter filter4( this );
+
+ if ( pHurt )
+ {
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ pHurt->ViewPunch( QAngle( -25, 8, 0) );
+
+ Vector vRight;
+ AngleVectors( GetAbsAngles(), NULL, &vRight, NULL );
+
+ // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above.
+ if ( pHurt->IsPlayer() )
+ {
+ // this is a player. Knock him around.
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + vRight * 250 );
+ }
+
+ EmitSound(filter4, entindex(), "AlienGrunt.AttackHit" );
+
+ Vector vecArmPos;
+ QAngle angArmAng;
+ GetAttachment( 0, vecArmPos, angArmAng );
+ SpawnBlood(vecArmPos, g_vecAttackDir, pHurt->BloodColor(), 25);// a little surface blood.
+ }
+ else
+ {
+ // Play a random attack miss sound
+ EmitSound(filter4, entindex(), "AlienGrunt.AttackMiss" );
+ }
+ }
+ break;
+
+ case AGRUNT_AE_RIGHT_PUNCH:
+ {
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, vecMins, vecMaxs, sk_agrunt_dmg_punch.GetFloat(), DMG_CLUB );
+ CPASAttenuationFilter filter5( this );
+
+ if ( pHurt )
+ {
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ pHurt->ViewPunch( QAngle( 25, 8, 0) );
+
+ // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above.
+ if ( pHurt->IsPlayer() )
+ {
+ // this is a player. Knock him around.
+ Vector vRight;
+ AngleVectors( GetAbsAngles(), NULL, &vRight, NULL );
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + vRight * -250 );
+ }
+
+ EmitSound( filter5, entindex(), "AlienGrunt.AttackHit" );
+
+ Vector vecArmPos;
+ QAngle angArmAng;
+ GetAttachment( 0, vecArmPos, angArmAng );
+ SpawnBlood(vecArmPos, g_vecAttackDir, pHurt->BloodColor(), 25);// a little surface blood.
+ }
+ else
+ {
+ // Play a random attack miss sound
+ EmitSound( filter5, entindex(), "AlienGrunt.AttackMiss" );
+ }
+ }
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+
+//=========================================================
+// DieSound
+//=========================================================
+void CNPC_AlienGrunt::DeathSound( const CTakeDamageInfo &info )
+{
+ StopTalking();
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "AlienGrunt.Die" );
+}
+
+//=========================================================
+// AlertSound
+//=========================================================
+void CNPC_AlienGrunt::AlertSound( void )
+{
+ StopTalking();
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "AlienGrunt.Alert" );
+}
+
+//=========================================================
+// AttackSound
+//=========================================================
+void CNPC_AlienGrunt::AttackSound( void )
+{
+ StopTalking();
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "AlienGrunt.Attack" );
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_AlienGrunt::PainSound( const CTakeDamageInfo &info )
+{
+ if ( m_flNextPainTime > gpGlobals->curtime )
+ {
+ return;
+ }
+
+ m_flNextPainTime = gpGlobals->curtime + 0.6;
+
+ StopTalking();
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(),"AlienGrunt.Pain" );
+}
+
+//=========================================================
+// ShouldSpeak - Should this agrunt be talking?
+//=========================================================
+bool CNPC_AlienGrunt::ShouldSpeak( void )
+{
+ if ( m_flNextSpeakTime > gpGlobals->curtime )
+ {
+ // my time to talk is still in the future.
+ return FALSE;
+ }
+
+ if ( m_spawnflags & SF_NPC_GAG )
+ {
+ if ( m_NPCState != NPC_STATE_COMBAT )
+ {
+ // if gagged, don't talk outside of combat.
+ // if not going to talk because of this, put the talk time
+ // into the future a bit, so we don't talk immediately after
+ // going into combat
+ m_flNextSpeakTime = gpGlobals->curtime + 3;
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+//=========================================================
+// PrescheduleThink
+//=========================================================
+void CNPC_AlienGrunt::PrescheduleThink ( void )
+{
+ BaseClass::PrescheduleThink();
+
+ if ( ShouldSpeak() )
+ {
+ if ( m_flNextWordTime < gpGlobals->curtime )
+ {
+ // play a new sound
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "AlienGrunt.Idle" );
+
+ // is this word our last?
+ if ( random->RandomInt( 1, 10 ) <= 1 )
+ {
+ // stop talking.
+ StopTalking();
+ }
+ else
+ {
+ m_flNextWordTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1 );
+ }
+ }
+ }
+}
+
+//=========================================================
+// FCanCheckAttacks - this is overridden for alien grunts
+// because they can use their smart weapons against unseen
+// enemies. Base class doesn't attack anyone it can't see.
+//=========================================================
+bool CNPC_AlienGrunt::FCanCheckAttacks ( void )
+{
+ if ( !HasCondition( COND_ENEMY_TOO_FAR ) )
+ return true;
+ else
+ return false;
+}
+
+//=========================================================
+// CheckMeleeAttack1 - alien grunts zap the crap out of
+// any enemy that gets too close.
+//=========================================================
+int CNPC_AlienGrunt::MeleeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( flDist > AGRUNT_MELEE_DIST )
+ return COND_NONE;
+
+ if ( flDot < 0.6 )
+ return COND_NONE;
+
+ if ( HasCondition ( COND_SEE_ENEMY ) && GetEnemy() != NULL )
+ return COND_CAN_MELEE_ATTACK1;
+
+ return COND_NONE;
+}
+
+//=========================================================
+// CheckRangeAttack1
+//
+// !!!LATER - we may want to load balance this. Several
+// tracelines are done, so we may not want to do this every
+// server frame. Definitely not while firing.
+//=========================================================
+int CNPC_AlienGrunt::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( gpGlobals->curtime < m_flNextHornetAttackCheck )
+ {
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ if ( m_fCanHornetAttack == true )
+ {
+ return COND_CAN_RANGE_ATTACK1;
+ }
+ else
+ {
+ return COND_NONE;
+ }
+ }
+ else
+ return COND_NONE;
+ }
+
+ if ( flDist < AGRUNT_MELEE_DIST )
+ return COND_NONE;
+
+ if ( flDist > 1024 )
+ return COND_NONE;
+
+ if ( flDot < 0.5 )
+ return COND_NONE;
+
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ trace_t tr;
+ Vector vecArmPos;
+ QAngle angArmDir;
+
+ // verify that a shot fired from the gun will hit the enemy before the world.
+ // !!!LATER - we may wish to do something different for projectile weapons as opposed to instant-hit
+ GetAttachment( "0", vecArmPos, angArmDir );
+ UTIL_TraceLine( vecArmPos, GetEnemy()->BodyTarget( vecArmPos ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ if ( tr.fraction == 1.0 || tr.m_pEnt == GetEnemy() )
+ {
+ m_flNextHornetAttackCheck = gpGlobals->curtime + random->RandomFloat( 2, 5 );
+ m_fCanHornetAttack = true;
+
+ return COND_CAN_RANGE_ATTACK1;
+ }
+ }
+
+ m_flNextHornetAttackCheck = gpGlobals->curtime + 0.2;// don't check for half second if this check wasn't successful
+ m_fCanHornetAttack = false;
+ return COND_NONE;
+}
+
+//=========================================================
+// StopTalking - won't speak again for 10-20 seconds.
+//=========================================================
+void CNPC_AlienGrunt::StopTalking( void )
+{
+ m_flNextWordTime = m_flNextSpeakTime = gpGlobals->curtime + 10 + random->RandomInt(0, 10);
+}
+
+
+void CNPC_AlienGrunt::StartTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_AGRUNT_RANGE_ATTACK1_NOTURN:
+ {
+ SetLastAttackTime( gpGlobals->curtime );
+ ResetIdealActivity( ACT_RANGE_ATTACK1 );
+ }
+ break;
+
+ case TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE:
+ {
+ Vector forward;
+ AngleVectors( GetAbsAngles(), &forward );
+ Vector flEnemyLKP = GetEnemyLKP();
+
+ GetNavigator()->SetGoal( flEnemyLKP - forward * 64, AIN_CLEAR_TARGET);
+
+ if ( GetNavigator()->SetGoal( flEnemyLKP - forward * 64, AIN_CLEAR_TARGET) )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ Msg ( "AGruntGetPathToEnemyCorpse failed!!\n" );
+ TaskFail( FAIL_NO_ROUTE );
+ }
+ }
+ break;
+
+ case TASK_AGRUNT_SETUP_HIDE_ATTACK:
+ // alien grunt shoots hornets back out into the open from a concealed location.
+ // try to find a spot to throw that gives the smart weapon a good chance of finding the enemy.
+ // ideally, this spot is along a line that is perpendicular to a line drawn from the agrunt to the enemy.
+
+ CHL1BaseNPC *pEnemyMonsterPtr;
+
+ pEnemyMonsterPtr = (CHL1BaseNPC *)GetEnemy()->MyNPCPointer();
+
+ if ( pEnemyMonsterPtr )
+ {
+ Vector vecCenter, vForward, vRight, vecEnemyLKP;
+ QAngle angTmp;
+ trace_t tr;
+ BOOL fSkip;
+
+ fSkip = FALSE;
+ vecCenter = WorldSpaceCenter();
+ vecEnemyLKP = GetEnemyLKP();
+
+ VectorAngles( vecEnemyLKP - GetAbsOrigin(), angTmp );
+ SetAbsAngles( angTmp );
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, NULL );
+
+ UTIL_TraceLine( WorldSpaceCenter() + vForward * 128, vecEnemyLKP, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ if ( tr.fraction == 1.0 )
+ {
+ GetMotor()->SetIdealYawToTargetAndUpdate ( GetAbsOrigin() + vRight * 128 );
+ fSkip = TRUE;
+ TaskComplete();
+ }
+
+ if ( !fSkip )
+ {
+ UTIL_TraceLine( WorldSpaceCenter() - vForward * 128, vecEnemyLKP, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ if ( tr.fraction == 1.0 )
+ {
+ GetMotor()->SetIdealYawToTargetAndUpdate ( GetAbsOrigin() - vRight * 128 );
+ fSkip = TRUE;
+ TaskComplete();
+ }
+ }
+
+ if ( !fSkip )
+ {
+ UTIL_TraceLine( WorldSpaceCenter() + vForward * 256, vecEnemyLKP, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ if ( tr.fraction == 1.0 )
+ {
+ GetMotor()->SetIdealYawToTargetAndUpdate ( GetAbsOrigin() + vRight * 256 );
+ fSkip = TRUE;
+ TaskComplete();
+ }
+ }
+
+ if ( !fSkip )
+ {
+ UTIL_TraceLine( WorldSpaceCenter() - vForward * 256, vecEnemyLKP, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
+ if ( tr.fraction == 1.0 )
+ {
+ GetMotor()->SetIdealYawToTargetAndUpdate ( GetAbsOrigin() - vRight * 256 );
+ fSkip = TRUE;
+ TaskComplete();
+ }
+ }
+
+ if ( !fSkip )
+ {
+ TaskFail( FAIL_NO_COVER );
+ }
+ }
+ else
+ {
+ Msg ( "AGRunt - no enemy monster ptr!!!\n" );
+ TaskFail( FAIL_NO_ENEMY );
+ }
+ break;
+
+ default:
+ BaseClass::StartTask ( pTask );
+ break;
+ }
+}
+
+
+void CNPC_AlienGrunt::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ // NOTE: This is obsolete. Don't use it for HL2 code
+ case TASK_AGRUNT_RANGE_ATTACK1_NOTURN:
+ {
+ AutoMovement( );
+
+ if ( IsActivityFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+
+ default:
+ BaseClass::RunTask( pTask );
+ }
+}
+
+
+
+//=========================================================
+// GetSchedule - Decides which type of schedule best suits
+// the monster's current state and conditions. Then calls
+// monster's member function to get a pointer to a schedule
+// of the proper type.
+//=========================================================
+int CNPC_AlienGrunt::SelectSchedule( void )
+{
+ if ( HasCondition( COND_HEAR_DANGER ) )
+ {
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+ }
+
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ {
+ // dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // call base class, all code to handle dead enemies is centralized there.
+ return BaseClass::SelectSchedule();
+ }
+
+ if ( HasCondition( COND_NEW_ENEMY) )
+ {
+ return SCHED_WAKE_ANGRY;
+ }
+
+ // zap player!
+ if ( HasCondition ( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ AttackSound();// this is a total hack. Should be parto f the schedule
+ return SCHED_MELEE_ATTACK1;
+ }
+
+ if ( HasCondition ( COND_HEAVY_DAMAGE ) )
+ {
+ return SCHED_SMALL_FLINCH;
+ }
+
+ // can attack
+ if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlotRange( AGRUNT_SQUAD_SLOT_HORNET1, AGRUNT_SQUAD_SLOT_HORNET2 ) )
+ {
+ return SCHED_RANGE_ATTACK1;
+ }
+
+ if ( OccupyStrategySlot ( AGRUNT_SQUAD_SLOT_CHASE ) )
+ {
+ return SCHED_CHASE_ENEMY;
+ }
+
+ return SCHED_STANDOFF;
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+int CNPC_AlienGrunt::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ case SCHED_TAKE_COVER_FROM_ENEMY:
+ return SCHED_AGRUNT_TAKE_COVER_FROM_ENEMY;
+ break;
+
+ /*case SCHED_RANGE_ATTACK1:
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ return SCHED_AGRUNT_RANGE_ATTACK;
+ // else
+ // return SCHED_AGRUNT_HIDDEN_RANGE_ATTACK;
+ break;*/
+
+ case SCHED_STANDOFF:
+ return SCHED_AGRUNT_STANDOFF;
+ break;
+
+ case SCHED_VICTORY_DANCE:
+ return SCHED_AGRUNT_VICTORY_DANCE;
+ break;
+ case SCHED_FAIL:
+ // no fail schedule specified, so pick a good generic one.
+ {
+ if ( GetEnemy() != NULL )
+ {
+ // I have an enemy
+ // !!!LATER - what if this enemy is really far away and i'm chasing him?
+ // this schedule will make me stop, face his last known position for 2
+ // seconds, and then try to move again
+ return SCHED_AGRUNT_COMBAT_FAIL;
+ }
+
+ return SCHED_AGRUNT_FAIL;
+ }
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+void CNPC_AlienGrunt::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo ainfo = info;
+
+ float flDamage = ainfo.GetDamage();
+
+ if ( ptr->hitgroup == 10 && (ainfo.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB)))
+ {
+ // hit armor
+ if ( m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0,10) < 1) )
+ {
+ CPVSFilter filter( ptr->endpos );
+ te->ArmorRicochet( filter, 0.0, &ptr->endpos, &ptr->plane.normal );
+ m_flDamageTime = gpGlobals->curtime;
+ }
+
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ Vector vecTracerDir = vecDir;
+
+ vecTracerDir.x += random->RandomFloat( -0.3, 0.3 );
+ vecTracerDir.y += random->RandomFloat( -0.3, 0.3 );
+ vecTracerDir.z += random->RandomFloat( -0.3, 0.3 );
+
+ vecTracerDir = vecTracerDir * -512;
+
+ Vector vEndPos = ptr->endpos + vecTracerDir;
+
+ UTIL_Tracer( ptr->endpos, vEndPos, ENTINDEX( edict() ) );
+ }
+
+ flDamage -= 20;
+ if (flDamage <= 0)
+ flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
+
+ ainfo.SetDamage( flDamage );
+ }
+ else
+ {
+ SpawnBlood( ptr->endpos, vecDir, BloodColor(), flDamage);// a little surface blood.
+ TraceBleed( flDamage, vecDir, ptr, ainfo.GetDamageType() );
+ }
+
+ AddMultiDamage( ainfo, this );
+}
+
+
+//=========================================================
+// AI Schedules Specific to this monster
+//=========================================================
+AI_BEGIN_CUSTOM_NPC( monster_alien_grunt, CNPC_AlienGrunt )
+
+ DECLARE_ACTIVITY( ACT_THREAT_DISPLAY )
+
+ DECLARE_TASK ( TASK_AGRUNT_SETUP_HIDE_ATTACK )
+ DECLARE_TASK ( TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE )
+ DECLARE_TASK ( TASK_AGRUNT_RANGE_ATTACK1_NOTURN )
+
+ DECLARE_SQUADSLOT( AGRUNT_SQUAD_SLOT_HORNET1 )
+ DECLARE_SQUADSLOT( AGRUNT_SQUAD_SLOT_HORNET2 )
+ DECLARE_SQUADSLOT( AGRUNT_SQUAD_SLOT_CHASE )
+
+ //=========================================================
+ // Fail Schedule
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_FAIL,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2"
+ " TASK_WAIT_PVS 0"
+ " "
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ )
+
+ //=========================================================
+ // Combat Fail Schedule
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_COMBAT_FAIL,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 2"
+ " TASK_WAIT_PVS 0"
+ " "
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ )
+
+ //=========================================================
+ // Standoff schedule. Used in combat when a monster is
+ // hiding in cover or the enemy has moved out of sight.
+ // Should we look around in this schedule?
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_STANDOFF,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 2"
+ " "
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_SEE_ENEMY"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // Suppress
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_SUPPRESS_HORNET,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_RANGE_ATTACK1 0"
+ )
+
+ //=========================================================
+ // primary range attacks
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_RANGE_ATTACK,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_HIDDEN_RANGE_ATTACK,
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_AGRUNT_STANDOFF"
+ " TASK_AGRUNT_SETUP_HIDE_ATTACK 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_AGRUNT_RANGE_ATTACK1_NOTURN 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // Take cover from enemy! Tries lateral cover before node
+ // cover!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_TAKE_COVER_FROM_ENEMY,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT 0.2"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_FACE_ENEMY 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // Victory dance!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_VICTORY_DANCE,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_AGRUNT_THREAT_DISPLAY"
+ " TASK_WAIT 0.2"
+ " TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCH"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_STAND"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_THREAT_DISPLAY"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCH"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_STAND"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_AGRUNT_THREAT_DISPLAY,
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_THREAT_DISPLAY"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_PLAYER"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_WORLD"
+ )
+
+AI_END_CUSTOM_NPC()
diff --git a/game/server/hl1/hl1_npc_apache.cpp b/game/server/hl1/hl1_npc_apache.cpp
new file mode 100644
index 0000000..ee48d73
--- /dev/null
+++ b/game/server/hl1/hl1_npc_apache.cpp
@@ -0,0 +1,766 @@
+//========= 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 "hl1_basegrenade.h"
+#include "animation.h"
+#include "IEffects.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "soundenvelope.h"
+#include "hl1_CBaseHelicopter.h"
+#include "ndebugoverlay.h"
+#include "smoke_trail.h"
+#include "beam_shared.h"
+#include "grenade_homer.h"
+
+#define HOMER_TRAIL0_LIFE 0.1
+#define HOMER_TRAIL1_LIFE 0.2
+#define HOMER_TRAIL2_LIFE 3.0// 1.0
+
+#define SF_NOTRANSITION 128
+
+extern short g_sModelIndexFireball;
+
+class CNPC_Apache : public CBaseHelicopter
+{
+ DECLARE_CLASS( CNPC_Apache, CBaseHelicopter );
+public:
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ int BloodColor( void ) { return DONT_BLEED; }
+ Class_T Classify( void ) { return CLASS_HUMAN_MILITARY; };
+ void InitializeRotorSound( void );
+ void LaunchRocket( Vector &viewDir, int damage, int radius, Vector vecLaunchPoint );
+
+ void Flight( void );
+
+ bool FireGun( void );
+ void AimRocketGun( void );
+ void FireRocket( void );
+ void DyingThink( void );
+
+ int ObjectCaps( void );
+
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+/* bool OnInternalDrawModel( ClientModelRenderInfo_t *pInfo )
+ {
+ BaseClass::OnInteralDrawModel( pInfo );
+ Vector origin = GetAbsOrigin();
+ origin.z += 32;
+ SetAbsOrigin( origin );
+ }*/
+
+/*( void SetAbsOrigin( const Vector& absOrigin )
+ {
+ ((Vector&)absOrigin).z += 32;
+ BaseClass::SetAbsOrigin( absOrigin );
+ }*/
+
+
+
+ /* int Save( CSave &save );
+ int Restore( CRestore &restore );
+ static TYPEDESCRIPTION m_SaveData[];
+
+ void Spawn( void );
+ void Precache( void );
+
+
+ void Killed( entvars_t *pevAttacker, int iGib );
+ void GibMonster( void );
+
+ void EXPORT HuntThink( void );
+ void EXPORT FlyTouch( CBaseEntity *pOther );
+ void EXPORT CrashTouch( CBaseEntity *pOther );
+ void EXPORT DyingThink( void );
+ void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ void EXPORT NullThink( void );
+
+ void ShowDamage( void );
+ void Flight( void );
+ void FireRocket( void );
+ BOOL FireGun( void );
+
+ 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 m_iRockets;
+ float m_flForce;
+ float m_flNextRocket;
+
+ int m_iAmmoType;
+
+ Vector m_vecTarget;
+ Vector m_posTarget;
+
+ Vector m_vecDesired;
+ Vector m_posDesired;
+
+ Vector m_vecGoal;
+
+ QAngle m_angGun;
+
+ int m_iSoundState; // don't save this
+
+ int m_iExplode;
+ int m_iBodyGibs;
+ int m_nDebrisModel;
+
+ float m_flGoalSpeed;
+
+ CHandle<SmokeTrail> m_hSmoke;
+ CBeam *m_pBeam;
+};
+
+BEGIN_DATADESC( CNPC_Apache )
+ DEFINE_FIELD( m_iRockets, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flForce, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextRocket, FIELD_TIME ),
+ 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_vecGoal, FIELD_VECTOR ),
+ DEFINE_FIELD( m_angGun, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flLastSeen, FIELD_TIME ),
+ DEFINE_FIELD( m_flPrevSeen, FIELD_TIME ),
+// DEFINE_FIELD( m_iSoundState, FIELD_INTEGER ),
+// DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
+// DEFINE_FIELD( m_iExplode, FIELD_INTEGER ),
+// DEFINE_FIELD( m_iBodyGibs, FIELD_INTEGER ),
+ DEFINE_FIELD( m_pBeam, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_flGoalSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_hSmoke, FIELD_EHANDLE ),
+END_DATADESC()
+
+ConVar sk_apache_health( "sk_apache_health","100");
+
+static Vector s_vecSurroundingMins( -300, -300, -172);
+static Vector s_vecSurroundingMaxs(300, 300, 8);
+
+
+void CNPC_Apache::Spawn( void )
+{
+ Precache( );
+ SetModel( "models/apache.mdl" );
+
+ BaseClass::Spawn();
+
+ AddFlag( FL_NPC );
+ SetSolid( SOLID_BBOX );
+ SetMoveType( MOVETYPE_STEP );
+ AddFlag( FL_FLY );
+
+
+ m_iHealth = sk_apache_health.GetFloat();
+
+ m_flFieldOfView = -0.707; // 270 degrees
+
+ m_fHelicopterFlags = BITS_HELICOPTER_MISSILE_ON | BITS_HELICOPTER_GUN_ON;
+
+ InitBoneControllers();
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ m_iRockets = 10;
+
+ UTIL_SetSize( this, Vector( -32, -32, -32 ), Vector( 32, 32, 32 ) );
+
+ //CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &s_vecSurroundingMins, &s_vecSurroundingMaxs );
+ //AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST );
+
+ m_hSmoke = NULL;
+}
+
+LINK_ENTITY_TO_CLASS ( monster_apache, CNPC_Apache );
+
+int CNPC_Apache::ObjectCaps( void )
+{
+ if ( GetSpawnFlags() & SF_NOTRANSITION )
+ return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION;
+ else
+ return BaseClass::ObjectCaps();
+}
+
+void CNPC_Apache::Precache( void )
+{
+ // Get to tha chopper!
+ PrecacheModel( "models/apache.mdl" );
+ PrecacheScriptSound( "Apache.Rotor" );
+ m_nDebrisModel = PrecacheModel( "models/metalplategibs_green.mdl" );
+
+ // Gun
+ PrecacheScriptSound( "Apache.FireGun" );
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ // Rockets
+ UTIL_PrecacheOther( "grenade_homer" );
+ PrecacheScriptSound( "Apache.RPG" );
+ PrecacheModel( "models/weapons/w_missile.mdl" );
+
+ BaseClass::Precache();
+}
+
+void CNPC_Apache::InitializeRotorSound( void )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+ m_pRotorSound = controller.SoundCreate( filter, entindex(), "Apache.Rotor" );
+
+ BaseClass::InitializeRotorSound();
+}
+
+void CNPC_Apache::Flight( void )
+{
+ StudioFrameAdvance( );
+
+ float flDistToDesiredPosition = (GetAbsOrigin() - m_vecDesiredPosition).Length();
+ // NDebugOverlay::Line(GetAbsOrigin(), m_vecDesiredPosition, 0,0,255, true, 0.1);
+
+ if (m_flGoalSpeed < 800 )
+ m_flGoalSpeed += GetAcceleration();
+
+
+// Vector vecGoalOrientation;
+ if (flDistToDesiredPosition > 250) // 500
+ {
+ Vector v1 = (m_vecTargetPosition - GetAbsOrigin());
+ Vector v2 = (m_vecDesiredPosition - GetAbsOrigin());
+
+ VectorNormalize( v1 );
+ VectorNormalize( v2 );
+
+ if (m_flLastSeen + 90 > gpGlobals->curtime && DotProduct( v1, v2 ) > 0.25)
+ {
+ m_vecGoalOrientation = ( m_vecTargetPosition - GetAbsOrigin());
+ }
+ else
+ {
+ m_vecGoalOrientation = (m_vecDesiredPosition - GetAbsOrigin());
+ }
+ VectorNormalize( m_vecGoalOrientation );
+ }
+ else
+ {
+ AngleVectors( GetGoalEnt()->GetAbsAngles(), &m_vecGoalOrientation );
+ }
+// SetGoalOrientation( vecGoalOrientation );
+
+
+ if (GetGoalEnt())
+ {
+ // ALERT( at_console, "%.0f\n", flLength );
+ if ( HasReachedTarget() )
+ {
+ // If we get this close to the desired position, it's assumed that we've reached
+ // the desired position, so move on.
+
+ // Fire target that I've reached my goal
+ m_AtTarget.FireOutput( GetGoalEnt(), this );
+
+ OnReachedTarget( GetGoalEnt() );
+
+ SetGoalEnt( gEntList.FindEntityByName( NULL, GetGoalEnt()->m_target ) );
+
+ if (GetGoalEnt())
+ {
+ m_vecDesiredPosition = GetGoalEnt()->GetAbsOrigin();
+
+// Vector vecGoalOrientation;
+ AngleVectors( GetGoalEnt()->GetAbsAngles(), &m_vecGoalOrientation );
+
+// SetGoalOrientation( vecGoalOrientation );
+
+ flDistToDesiredPosition = (GetAbsOrigin() - m_vecDesiredPosition).Length();
+ }
+ }
+ }
+ else
+ {
+ // If we can't find a new target, just stay where we are.
+ m_vecDesiredPosition = GetAbsOrigin();
+ }
+
+ // tilt model 5 degrees
+ QAngle angAdj = QAngle( 5.0, 0, 0 );
+
+ // estimate where I'll be facing in one seconds
+ Vector forward, right, up;
+ AngleVectors( GetAbsAngles() + GetLocalAngularVelocity() * 2 + angAdj, &forward, &right, &up );
+ // Vector vecEst1 = GetAbsOrigin() + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 );
+ // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right );
+
+
+ QAngle angVel = GetLocalAngularVelocity();
+ float flSide = DotProduct( m_vecGoalOrientation, right );
+
+ if (flSide < 0)
+ {
+ if ( angVel.y < 60)
+ {
+ angVel.y += 8; // 9 * (3.0/2.0);
+ }
+ }
+ else
+ {
+ if ( angVel.y > -60)
+ {
+ angVel.y -= 8; // 9 * (3.0/2.0);
+ }
+ }
+ angVel.y *= 0.98;
+ SetLocalAngularVelocity( angVel );
+
+ Vector vecVel = GetAbsVelocity();
+
+ // estimate where I'll be in two seconds
+ AngleVectors( GetAbsAngles() + GetLocalAngularVelocity() * 1 + angAdj, &forward, &right, &up );
+ Vector vecEst = GetAbsOrigin() + vecVel * 2.0 + up * m_flForce * 20 - Vector( 0, 0, 384 * 2 );
+
+ // add immediate force
+ AngleVectors( GetAbsAngles() + angAdj, &forward, &right, &up );
+
+ vecVel.x += up.x * m_flForce;
+ vecVel.y += up.y * m_flForce;
+ vecVel.z += up.z * m_flForce;
+ // add gravity
+ vecVel.z -= 38.4; // 32ft/sec
+
+ float flSpeed = vecVel.Length();
+ float flDir = DotProduct( Vector( forward.x, forward.y, 0 ), Vector( vecVel.x, vecVel.y, 0 ) );
+ if (flDir < 0)
+ flSpeed = -flSpeed;
+
+ float flDist = DotProduct( m_vecDesiredPosition - vecEst, forward );
+
+ // float flSlip = DotProduct( pev->velocity, gpGlobals->v_right );
+ float flSlip = -DotProduct( m_vecDesiredPosition - vecEst, right );
+
+ angVel = GetLocalAngularVelocity();
+ // fly sideways
+ if (flSlip > 0)
+ {
+ if (GetAbsAngles().z > -30 && angVel.z > -15)
+ angVel.z -= 4;
+ else
+ angVel.z += 2;
+ }
+ else
+ {
+
+ if (GetAbsAngles().z < 30 && angVel.z < 15)
+ angVel.z += 4;
+ else
+ angVel.z -= 2;
+ }
+ SetLocalAngularVelocity( angVel );
+
+ // sideways drag
+ vecVel.x = vecVel.x * (1.0 - fabs( right.x ) * 0.05);
+ vecVel.y = vecVel.y * (1.0 - fabs( right.y ) * 0.05);
+ vecVel.z = vecVel.z * (1.0 - fabs( right.z ) * 0.05);
+
+ // general drag
+ vecVel = vecVel * 0.995;
+
+ // Set final computed velocity
+ SetAbsVelocity( vecVel );
+
+ // apply power to stay correct height
+ if (m_flForce < 80 && vecEst.z < m_vecDesiredPosition.z)
+ {
+ m_flForce += 12;
+ }
+ else if (m_flForce > 30)
+ {
+ if (vecEst.z > m_vecDesiredPosition.z)
+ m_flForce -= 8;
+ }
+
+
+ angVel = GetLocalAngularVelocity();
+ // pitch forward or back to get to target
+ if (flDist > 0 && flSpeed < m_flGoalSpeed && GetAbsAngles().x + angVel.x < 40)
+ {
+ // ALERT( at_console, "F " );
+ // lean forward
+ angVel.x += 12.0;
+ }
+ else if (flDist < 0 && flSpeed > -50 && GetAbsAngles().x + angVel.x > -20)
+ {
+ // ALERT( at_console, "B " );
+ // lean backward
+ angVel.x -= 12.0;
+ }
+ else if (GetAbsAngles().x + angVel.x < 0)
+ {
+ // ALERT( at_console, "f " );
+ angVel.x += 4.0;
+ }
+ else if (GetAbsAngles().x + angVel.x > 0)
+ {
+ // ALERT( at_console, "b " );
+ angVel.x -= 4.0;
+ }
+
+ // Set final computed angular velocity
+ SetLocalAngularVelocity( angVel );
+
+ // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", GetAbsOrigin().x, pev->velocity.x, flDist, flSpeed, GetAbsAngles().x, pev->avelocity.x, m_flForce );
+ // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", GetAbsOrigin().z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce );
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+
+#define CHOPPER_AP_GUN_TIP 0
+#define CHOPPER_AP_GUN_BASE 1
+
+#define CHOPPER_BC_GUN_YAW 0
+#define CHOPPER_BC_GUN_PITCH 1
+#define CHOPPER_BC_POD_PITCH 2
+
+bool CNPC_Apache::FireGun( )
+{
+ if ( !GetEnemy() )
+ return false;
+
+ Vector vForward, vRight, vUp;
+
+ AngleVectors( GetAbsAngles(), &vForward, &vUp, &vRight );
+
+ Vector posGun;
+ QAngle angGun;
+ GetAttachment( 1, posGun, angGun );
+
+ Vector vecTarget = (m_vecTargetPosition - posGun);
+
+ VectorNormalize( vecTarget );
+
+ Vector vecOut;
+
+ vecOut.x = DotProduct( vForward, vecTarget );
+ vecOut.y = -DotProduct( vUp, vecTarget );
+ vecOut.z = DotProduct( vRight, vecTarget );
+
+ QAngle angles;
+
+ VectorAngles( vecOut, angles );
+
+ angles.y = AngleNormalize(angles.y);
+ angles.x = AngleNormalize(angles.x);
+
+ if (angles.x > m_angGun.x)
+ m_angGun.x = MIN( angles.x, m_angGun.x + 12 );
+ if (angles.x < m_angGun.x)
+ m_angGun.x = MAX( angles.x, m_angGun.x - 12 );
+ if (angles.y > m_angGun.y)
+ m_angGun.y = MIN( angles.y, m_angGun.y + 12 );
+ if (angles.y < m_angGun.y)
+ m_angGun.y = MAX( angles.y, m_angGun.y - 12 );
+
+ // hacks - shouldn't be hardcoded, oh well.
+ // limit it so it doesn't pop if you try to set it to the max value
+ m_angGun.y = clamp( m_angGun.y, -89.9, 89.9 );
+ m_angGun.x = clamp( m_angGun.x, -9.9, 44.9 );
+
+ m_angGun.y = SetBoneController( 0, m_angGun.y );
+ m_angGun.x = SetBoneController( 1, m_angGun.x );
+
+ Vector posBarrel;
+ QAngle angBarrel;
+ GetAttachment( 0, posBarrel, angBarrel );
+
+ Vector forward;
+ AngleVectors( angBarrel + m_angGun, &forward );
+
+ Vector2D vec2LOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).AsVector2D();
+ vec2LOS.NormalizeInPlace();
+
+ float flDot = vec2LOS.Dot( forward.AsVector2D() );
+
+ //forward
+// NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( forward * 200 ), 255,0,0, false, 0.1);
+ //LOS
+// NDebugOverlay::Line( posGun, m_vecTargetPosition , 0,0,255, false, 0.1);
+// NDebugOverlay::Box( GetAbsOrigin(), s_vecSurroundingMins, s_vecSurroundingMaxs, 0, 255,0, false, 0.1);
+
+ if ( flDot > 0.98 )
+ {
+ CPASAttenuationFilter filter( this, 0.2f );
+
+ EmitSound( filter, entindex(), "Apache.FireGun" );//<<TEMP>>temp sound
+
+ // gun is a bit dodgy, just fire at the target if we are close
+ FireBullets( 1, posGun, vecTarget, VECTOR_CONE_4DEGREES, 8192, m_iAmmoType, 2 );
+
+ return true;
+ }
+
+ return false;
+}
+
+void CNPC_Apache::FireRocket( void )
+{
+ static float side = 1.0;
+ static int count;
+ Vector vForward, vRight, vUp;
+
+
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+ Vector vecSrc = GetAbsOrigin() + 1.5 * ( vForward * 21 + vRight * 70 * side + vUp * -79 );
+
+ // pick firing pod to launch from
+ switch( m_iRockets % 5)
+ {
+ case 0: vecSrc = vecSrc + vRight * 10; break;
+ case 1: vecSrc = vecSrc - vRight * 10; break;
+ case 2: vecSrc = vecSrc + vUp * 10; break;
+ case 3: vecSrc = vecSrc - vUp * 10; break;
+ case 4: break;
+ }
+
+ Vector vTargetDir = m_vecTargetPosition - GetAbsOrigin();
+ VectorNormalize ( vTargetDir );
+ LaunchRocket( vTargetDir, 100, 150, vecSrc);
+
+ m_iRockets--;
+
+ side = - side;
+}
+void CNPC_Apache::AimRocketGun( void )
+{
+ Vector vForward, vRight, vUp;
+
+ if (m_iRockets <= 0)
+ return;
+
+ Vector vTargetDir = m_vecTargetPosition - GetAbsOrigin();
+ VectorNormalize ( vTargetDir );
+
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+ Vector vecEst = ( vForward * 800 + GetAbsVelocity());
+ VectorNormalize ( vecEst );
+
+ trace_t tr1;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecEst * 4096, MASK_ALL, this, COLLISION_GROUP_NONE, &tr1);
+
+// NDebugOverlay::Line(GetAbsOrigin(), tr1.endpos, 255,255,0, false, 0.1);
+
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vTargetDir * 4096, MASK_ALL, this, COLLISION_GROUP_NONE, &tr1);
+
+// NDebugOverlay::Line(GetAbsOrigin(), tr1.endpos, 0,255,0, false, 0.1);
+
+ // ALERT( at_console, "%d %d %d %4.2f\n", GetAbsAngles().x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->curtime, DotProduct( m_vecTarget, vecEst ) );
+
+ if ((m_iRockets % 2) == 1)
+ {
+ FireRocket( );
+ m_flNextRocket = gpGlobals->curtime + 0.5;
+ if (m_iRockets <= 0)
+ {
+ m_flNextRocket = gpGlobals->curtime + 10;
+ m_iRockets = 10;
+ }
+ }
+ else if (DotProduct( GetAbsVelocity(), vForward ) > -100 && m_flNextRocket < gpGlobals->curtime)
+ {
+ if (m_flLastSeen + 60 > gpGlobals->curtime)
+ {
+ if (GetEnemy() != NULL)
+ {
+ // make sure it's a good shot
+ //if (DotProduct( vTargetDir, vecEst) > 0.5)
+ {
+ trace_t tr;
+
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecEst * 4096, MASK_ALL, this, COLLISION_GROUP_NONE, &tr);
+
+ // NDebugOverlay::Line(GetAbsOrigin(), tr.endpos, 255,0,255, false, 5);
+
+// if ((tr.endpos - m_vecTargetPosition).Length() < 512)
+ if ((tr.endpos - m_vecTargetPosition).Length() < 1024)
+ FireRocket( );
+ }
+ }
+ else
+ {
+ trace_t tr;
+
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + vecEst * 4096, MASK_ALL, this, COLLISION_GROUP_NONE, &tr);
+ // just fire when close
+ if ((tr.endpos - m_vecTargetPosition).Length() < 512)
+ FireRocket( );
+ }
+ }
+ }
+}
+
+#define MISSILE_HOMING_STRENGTH 0.3
+#define MISSILE_HOMING_DELAY 5.0
+#define MISSILE_HOMING_RAMP_UP 0.5
+#define MISSILE_HOMING_DURATION 1.0
+#define MISSILE_HOMING_RAMP_DOWN 0.5
+
+void CNPC_Apache::LaunchRocket( Vector &viewDir, int damage, int radius, Vector vecLaunchPoint )
+{
+
+ CGrenadeHomer *pGrenade = CGrenadeHomer::CreateGrenadeHomer(
+ MAKE_STRING("models/weapons/w_missile.mdl"),
+ MAKE_STRING( "Apache.RPG" ),
+ vecLaunchPoint, vec3_angle, edict() );
+ pGrenade->Spawn( );
+ pGrenade->SetHoming(MISSILE_HOMING_STRENGTH, MISSILE_HOMING_DELAY,
+ MISSILE_HOMING_RAMP_UP, MISSILE_HOMING_DURATION,
+ MISSILE_HOMING_RAMP_DOWN);
+ pGrenade->SetDamage( damage );
+ pGrenade->m_DmgRadius = radius;
+
+ pGrenade->Launch( this, GetEnemy(), viewDir * 1500, 500, 0, HOMER_SMOKE_TRAIL_ON );
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Lame, temporary death
+//-----------------------------------------------------------------------------
+void CNPC_Apache::DyingThink( void )
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if( gpGlobals->curtime > m_flNextCrashExplosion )
+ {
+ CPASFilter pasFilter( GetAbsOrigin() );
+ Vector pos;
+
+ pos = GetAbsOrigin();
+ pos.x += random->RandomFloat( -150, 150 );
+ pos.y += random->RandomFloat( -150, 150 );
+ pos.z += random->RandomFloat( -150, -50 );
+
+ te->Explosion( pasFilter, 0.0, &pos, g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0 );
+
+ Vector vecSize = Vector( 500, 500, 60 );
+ CPVSFilter pvsFilter( GetAbsOrigin() );
+
+ te->BreakModel( pvsFilter, 0.0, GetAbsOrigin(), vec3_angle, vecSize, vec3_origin,
+ m_nDebrisModel, 100, 0, 2.5, BREAK_METAL );
+
+ m_flNextCrashExplosion = gpGlobals->curtime + random->RandomFloat( 0.3, 0.5 );
+ }
+
+ QAngle angVel = GetLocalAngularVelocity();
+ if( angVel.y < 400 )
+ {
+ angVel.y *= 1.1;
+ SetLocalAngularVelocity( angVel );
+ }
+
+ Vector vecImpulse( 0, 0, -38.4 ); // gravity - 32ft/sec
+ ApplyAbsVelocityImpulse( vecImpulse );
+
+ if( m_hSmoke )
+ {
+ m_hSmoke->SetLifetime(0.1f);
+ m_hSmoke = NULL;
+ }
+
+}
+
+
+
+void CNPC_Apache::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+
+ CTakeDamageInfo dmgInfo = info;
+
+ // HITGROUPS don't work currently.
+ // ignore blades
+ //if (ptr->hitgroup == 6 && (info.GetDamageType() & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB)))
+ // return;
+
+ // hit hard, hits cockpit
+ if (info.GetDamage() > 50 || ptr->hitgroup == 1 || ptr->hitgroup == 2 || ptr->hitgroup == 3 )
+ {
+ // ALERT( at_console, "%map .0f\n", flDamage );
+ AddMultiDamage( dmgInfo, this );
+
+ if ( info.GetDamage() > 50 )
+ {
+ if ( m_hSmoke == NULL && (m_hSmoke = SmokeTrail::CreateSmokeTrail()) != NULL )
+ {
+ m_hSmoke->m_Opacity = 1.0f;
+ m_hSmoke->m_SpawnRate = 60;
+ m_hSmoke->m_ParticleLifetime = 1.3f;
+ m_hSmoke->m_StartColor.Init( 0.65f, 0.65f , 0.65f );
+ m_hSmoke->m_EndColor.Init( 0.65f, 0.65f, 0.65f );
+ m_hSmoke->m_StartSize = 12;
+ m_hSmoke->m_EndSize = 64;
+ m_hSmoke->m_SpawnRadius = 8;
+ m_hSmoke->m_MinSpeed = 2;
+ m_hSmoke->m_MaxSpeed = 24;
+
+ m_hSmoke->SetLifetime( 1e6 );
+ m_hSmoke->FollowEntity( this );
+
+ }
+ }
+ }
+ else
+ {
+ // do half damage in the body
+ dmgInfo.ScaleDamage(0.5);
+ AddMultiDamage( dmgInfo, this );
+ g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal );
+ }
+
+ if ( m_iHealth < 10 )
+ {
+ if ( m_hSmoke == NULL && (m_hSmoke = SmokeTrail::CreateSmokeTrail()) != NULL )
+ {
+ m_hSmoke->m_Opacity = 1.0f;
+ m_hSmoke->m_SpawnRate = 60;
+ m_hSmoke->m_ParticleLifetime = 1.3f;
+ m_hSmoke->m_StartColor.Init( 0.65f, 0.65f , 0.65f );
+ m_hSmoke->m_EndColor.Init( 0.65f, 0.65f, 0.65f );
+ m_hSmoke->m_StartSize = 12;
+ m_hSmoke->m_EndSize = 64;
+ m_hSmoke->m_SpawnRadius = 8;
+ m_hSmoke->m_MinSpeed = 2;
+ m_hSmoke->m_MaxSpeed = 24;
+
+ m_hSmoke->SetLifetime( 1e6 );
+ m_hSmoke->FollowEntity( this );
+
+ }
+ }
+}
diff --git a/game/server/hl1/hl1_npc_barnacle.cpp b/game/server/hl1/hl1_npc_barnacle.cpp
new file mode 100644
index 0000000..05524cb
--- /dev/null
+++ b/game/server/hl1/hl1_npc_barnacle.cpp
@@ -0,0 +1,516 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: barnacle - stationary ceiling mounted 'fishing' monster
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "hl1_npc_barnacle.h"
+#include "npcevent.h"
+#include "gib.h"
+#include "ai_default.h"
+#include "activitylist.h"
+#include "hl2_player.h"
+#include "vstdlib/random.h"
+#include "physics_saverestore.h"
+#include "vcollide_parse.h"
+#include "engine/IEngineSound.h"
+
+ConVar sk_barnacle_health( "sk_barnacle_health","25");
+
+//-----------------------------------------------------------------------------
+// Private activities.
+//-----------------------------------------------------------------------------
+static int ACT_EAT = 0;
+
+//-----------------------------------------------------------------------------
+// Interactions
+//-----------------------------------------------------------------------------
+int g_interactionBarnacleVictimDangle = 0;
+int g_interactionBarnacleVictimReleased = 0;
+int g_interactionBarnacleVictimGrab = 0;
+
+LINK_ENTITY_TO_CLASS( monster_barnacle, CNPC_Barnacle );
+IMPLEMENT_CUSTOM_AI( monster_barnacle, CNPC_Barnacle );
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize the custom schedules
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CNPC_Barnacle::InitCustomSchedules(void)
+{
+ INIT_CUSTOM_AI(CNPC_Barnacle);
+
+ ADD_CUSTOM_ACTIVITY(CNPC_Barnacle, ACT_EAT);
+
+ g_interactionBarnacleVictimDangle = CBaseCombatCharacter::GetInteractionID();
+ g_interactionBarnacleVictimReleased = CBaseCombatCharacter::GetInteractionID();
+ g_interactionBarnacleVictimGrab = CBaseCombatCharacter::GetInteractionID();
+}
+
+
+BEGIN_DATADESC( CNPC_Barnacle )
+
+ DEFINE_FIELD( m_flAltitude, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flKillVictimTime, FIELD_TIME ),
+ DEFINE_FIELD( m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something.
+ DEFINE_FIELD( m_fLiftingPrey, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flTongueAdj, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flIgnoreTouchesUntil, FIELD_TIME ),
+
+ // Function pointers
+ DEFINE_THINKFUNC( BarnacleThink ),
+ DEFINE_THINKFUNC( WaitTillDead ),
+END_DATADESC()
+
+
+//=========================================================
+// Classify - indicates this monster's place in the
+// relationship table.
+//=========================================================
+Class_T CNPC_Barnacle::Classify ( void )
+{
+ return CLASS_ALIEN_MONSTER;
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//
+// Returns number of events handled, 0 if none.
+//=========================================================
+void CNPC_Barnacle::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case BARNACLE_AE_PUKEGIB:
+ CGib::SpawnRandomGibs( this, 1, GIB_HUMAN );
+ break;
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Barnacle::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/barnacle.mdl" );
+ UTIL_SetSize( this, Vector(-16, -16, -32), Vector(16, 16, 0) );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_NONE );
+ SetBloodColor( BLOOD_COLOR_GREEN );
+ m_iHealth = sk_barnacle_health.GetFloat();
+ m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+ m_flKillVictimTime = 0;
+ m_cGibs = 0;
+ m_fLiftingPrey = FALSE;
+ m_takedamage = DAMAGE_YES;
+
+ InitBoneControllers();
+ InitTonguePosition();
+
+ // set eye position
+ SetDefaultEyeOffset();
+
+ SetActivity ( ACT_IDLE );
+
+ SetThink ( &CNPC_Barnacle::BarnacleThink );
+ SetNextThink( gpGlobals->curtime + 0.5f );
+ //Do not have a shadow
+ AddEffects( EF_NOSHADOW );
+
+ m_flIgnoreTouchesUntil = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+int CNPC_Barnacle::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ CTakeDamageInfo info = inputInfo;
+ if ( info.GetDamageType() & DMG_CLUB )
+ {
+ info.SetDamage( m_iHealth );
+ }
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Initialize tongue position when first spawned
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CNPC_Barnacle::InitTonguePosition( void )
+{
+ CBaseEntity *pTouchEnt;
+ float flLength;
+
+ pTouchEnt = TongueTouchEnt( &flLength );
+ m_flAltitude = flLength;
+
+ Vector origin;
+ QAngle angle;
+
+ GetAttachment( "TongueEnd", origin, angle );
+
+ m_flTongueAdj = origin.z - GetAbsOrigin().z;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Barnacle::BarnacleThink ( void )
+{
+ CBaseEntity *pTouchEnt;
+ float flLength;
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI)
+ {
+ // AI Disabled, don't do anything
+ }
+ else if ( GetEnemy() != NULL )
+ {
+// barnacle has prey.
+
+ if ( !GetEnemy()->IsAlive() )
+ {
+ // someone (maybe even the barnacle) killed the prey. Reset barnacle.
+ m_fLiftingPrey = FALSE;// indicate that we're not lifting prey.
+ SetEnemy( NULL );
+ return;
+ }
+
+ CBaseCombatCharacter* pVictim = GetEnemyCombatCharacterPointer();
+ Assert( pVictim );
+
+ if ( m_fLiftingPrey )
+ {
+
+ if ( GetEnemy() != NULL && pVictim->m_lifeState == LIFE_DEAD )
+ {
+ // crap, someone killed the prey on the way up.
+ SetEnemy( NULL );
+ m_fLiftingPrey = FALSE;
+ return;
+ }
+
+ // still pulling prey.
+ Vector vecNewEnemyOrigin = GetEnemy()->GetLocalOrigin();
+ vecNewEnemyOrigin.x = GetLocalOrigin().x;
+ vecNewEnemyOrigin.y = GetLocalOrigin().y;
+
+ // guess as to where their neck is
+ // FIXME: remove, ask victim where their neck is
+ vecNewEnemyOrigin.x -= 6 * cos(GetEnemy()->GetLocalAngles().y * M_PI/180.0);
+ vecNewEnemyOrigin.y -= 6 * sin(GetEnemy()->GetLocalAngles().y * M_PI/180.0);
+
+ m_flAltitude -= BARNACLE_PULL_SPEED;
+ vecNewEnemyOrigin.z += BARNACLE_PULL_SPEED;
+
+ if ( fabs( GetLocalOrigin().z - ( vecNewEnemyOrigin.z + GetEnemy()->GetViewOffset().z ) ) < BARNACLE_BODY_HEIGHT )
+ {
+ // prey has just been lifted into position ( if the victim origin + eye height + 8 is higher than the bottom of the barnacle, it is assumed that the head is within barnacle's body )
+ m_fLiftingPrey = FALSE;
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Barnacle.Bite");
+
+ // Take a while to kill the player
+ m_flKillVictimTime = gpGlobals->curtime + 10;
+
+ if ( pVictim )
+ {
+ pVictim->DispatchInteraction( g_interactionBarnacleVictimDangle, NULL, this );
+ SetActivity ( (Activity)ACT_EAT );
+ }
+ }
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ trace_t trace;
+ UTIL_TraceEntity( pEnemy, pEnemy->GetAbsOrigin(), vecNewEnemyOrigin, MASK_SOLID_BRUSHONLY, pEnemy, COLLISION_GROUP_NONE, &trace );
+
+ if( trace.fraction != 1.0 )
+ {
+ // The victim cannot be moved from their current origin to this new origin. So drop them.
+ SetEnemy( NULL );
+ m_fLiftingPrey = FALSE;
+
+ if( pEnemy->MyCombatCharacterPointer() )
+ {
+ pEnemy->MyCombatCharacterPointer()->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
+ }
+
+ // Ignore touches long enough to let the victim move away.
+ m_flIgnoreTouchesUntil = gpGlobals->curtime + 1.5;
+
+ SetActivity( ACT_IDLE );
+
+ return;
+ }
+
+ UTIL_SetOrigin ( GetEnemy(), vecNewEnemyOrigin );
+ }
+ else
+ {
+ // prey is lifted fully into feeding position and is dangling there.
+
+ if ( m_flKillVictimTime != -1 && gpGlobals->curtime > m_flKillVictimTime )
+ {
+ // kill!
+ if ( pVictim )
+ {
+ // DMG_CRUSH added so no physics force is generated
+ pVictim->TakeDamage( CTakeDamageInfo( this, this, pVictim->m_iHealth, DMG_SLASH | DMG_ALWAYSGIB | DMG_CRUSH ) );
+ m_cGibs = 3;
+ }
+
+ return;
+ }
+
+ // bite prey every once in a while
+ if ( pVictim && ( random->RandomInt( 0, 49 ) == 0 ) )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Barnacle.Chew" );
+
+ if ( pVictim )
+ {
+ pVictim->DispatchInteraction( g_interactionBarnacleVictimDangle, NULL, this );
+ }
+ }
+ }
+ }
+ else
+ {
+// barnacle has no prey right now, so just idle and check to see if anything is touching the tongue.
+
+ // If idle and no nearby client, don't think so often. Client should be out of PVS and not within 50 feet.
+ if ( !UTIL_FindClientInPVS(edict()) )
+ {
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
+
+ if( pPlayer )
+ {
+ Vector vecDist = pPlayer->GetAbsOrigin() - GetAbsOrigin();
+
+ if( vecDist.Length2DSqr() >= Square(600.0f) )
+ {
+ SetNextThink( gpGlobals->curtime + 1.5f );
+ }
+ }
+ }
+
+ if ( IsActivityFinished() )
+ {// this is done so barnacle will fidget.
+ SetActivity ( ACT_IDLE );
+ }
+
+ if ( m_cGibs && random->RandomInt(0,99) == 1 )
+ {
+ // cough up a gib.
+ CGib::SpawnRandomGibs( this, 1, GIB_HUMAN );
+ m_cGibs--;
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Barnacle.Chew" );
+ }
+
+ pTouchEnt = TongueTouchEnt( &flLength );
+
+ //NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, flLength ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,0, 0, 0.1 );
+
+ if ( pTouchEnt != NULL )
+ {
+ // tongue is fully extended, and is touching someone.
+ CBaseCombatCharacter* pBCC = (CBaseCombatCharacter *)pTouchEnt;
+
+ // FIXME: humans should return neck position
+ Vector vecGrabPos = pTouchEnt->GetAbsOrigin();
+
+ if ( pBCC && pBCC->DispatchInteraction( g_interactionBarnacleVictimGrab, &vecGrabPos, this ) )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Barnacle.Alert" );
+
+ SetSequenceByName ( "attack1" );
+
+ SetEnemy( pTouchEnt );
+
+ pTouchEnt->SetMoveType( MOVETYPE_FLY );
+ pTouchEnt->SetAbsVelocity( vec3_origin );
+ pTouchEnt->SetBaseVelocity( vec3_origin );
+ Vector origin = GetAbsOrigin();
+ origin.z = pTouchEnt->GetAbsOrigin().z;
+ pTouchEnt->SetLocalOrigin( origin );
+
+ m_fLiftingPrey = TRUE;// indicate that we should be lifting prey.
+ m_flKillVictimTime = -1;// set this to a bogus time while the victim is lifted.
+
+ m_flAltitude = (GetAbsOrigin().z - vecGrabPos.z);
+ }
+ }
+ else
+ {
+ // calculate a new length for the tongue to be clear of anything else that moves under it.
+ if ( m_flAltitude < flLength )
+ {
+ // if tongue is higher than is should be, lower it kind of slowly.
+ m_flAltitude += BARNACLE_PULL_SPEED;
+ }
+ else
+ {
+ m_flAltitude = flLength;
+ }
+
+ }
+
+ }
+
+ // ALERT( at_console, "tounge %f\n", m_flAltitude + m_flTongueAdj );
+ //NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, m_flAltitude ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,255,255, 0, 0.1 );
+
+ SetBoneController( 0, -(m_flAltitude + m_flTongueAdj) );
+ StudioFrameAdvance();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Barnacle::Event_Killed( const CTakeDamageInfo &info )
+{
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ m_takedamage = DAMAGE_NO;
+ m_lifeState = LIFE_DEAD;
+ if ( GetEnemy() != NULL )
+ {
+ CBaseCombatCharacter *pVictim = GetEnemyCombatCharacterPointer();
+
+ if ( pVictim )
+ {
+ pVictim->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
+ }
+ }
+
+ CGib::SpawnRandomGibs( this, 4, GIB_HUMAN );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Barnacle.Die" );
+
+ SetActivity ( ACT_DIESIMPLE );
+ SetBoneController( 0, 0 );
+
+ StudioFrameAdvance();
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ SetThink ( &CNPC_Barnacle::WaitTillDead );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Barnacle::WaitTillDead ( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ StudioFrameAdvance();
+ DispatchAnimEvents ( this );
+
+ if ( IsActivityFinished() )
+ {
+ // death anim finished.
+ StopAnimation();
+ SetThink ( NULL );
+ }
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Barnacle::Precache()
+{
+ PrecacheModel("models/barnacle.mdl");
+
+ PrecacheScriptSound( "Barnacle.Bite" );
+ PrecacheScriptSound( "Barnacle.Chew" );
+ PrecacheScriptSound( "Barnacle.Alert" );
+ PrecacheScriptSound( "Barnacle.Die" );
+
+ BaseClass::Precache();
+}
+
+//=========================================================
+// TongueTouchEnt - does a trace along the barnacle's tongue
+// to see if any entity is touching it. Also stores the length
+// of the trace in the int pointer provided.
+//=========================================================
+#define BARNACLE_CHECK_SPACING 8
+CBaseEntity *CNPC_Barnacle::TongueTouchEnt ( float *pflLength )
+{
+ trace_t tr;
+ float length;
+
+ // trace once to hit architecture and see if the tongue needs to change position.
+ UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() - Vector ( 0 , 0 , 2048 ),
+ MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ length = fabs( GetAbsOrigin().z - tr.endpos.z );
+ // Pull it up a tad
+ length -= 16;
+ if ( pflLength )
+ {
+ *pflLength = length;
+ }
+
+ // Don't try to touch any prey.
+ if ( m_flIgnoreTouchesUntil > gpGlobals->curtime )
+ return NULL;
+
+ Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 );
+ Vector mins = GetAbsOrigin() - delta;
+ Vector maxs = GetAbsOrigin() + delta;
+ maxs.z = GetAbsOrigin().z;
+
+ // Take our current tongue's length or a point higher if we hit a wall
+ // NOTENOTE: (this relieves the need to know if the tongue is currently moving)
+ mins.z -= MIN( m_flAltitude, length );
+
+ CBaseEntity *pList[10];
+ int count = UTIL_EntitiesInBox( pList, 10, mins, maxs, (FL_CLIENT|FL_NPC) );
+ if ( count )
+ {
+ for ( int i = 0; i < count; i++ )
+ {
+ CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pList[ i ] );
+
+ bool bCanHurt = false;
+
+ if ( IRelationType( pList[i] ) == D_HT || IRelationType( pList[i] ) == D_FR )
+ bCanHurt = true;
+
+ if ( pList[i] != this && bCanHurt == true && pVictim->m_lifeState == LIFE_ALIVE )
+ {
+ return pList[i];
+ }
+ }
+ }
+
+ return NULL;
+}
diff --git a/game/server/hl1/hl1_npc_barnacle.h b/game/server/hl1/hl1_npc_barnacle.h
new file mode 100644
index 0000000..3877857
--- /dev/null
+++ b/game/server/hl1/hl1_npc_barnacle.h
@@ -0,0 +1,63 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base combat character with no AI
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef HL1_NPC_BARNACLE_H
+#define HL1_NPC_BARNACLE_H
+
+#include "hl1_ai_basenpc.h"
+#include "rope_physics.h"
+
+#define BARNACLE_BODY_HEIGHT 44 // how 'tall' the barnacle's model is.
+#define BARNACLE_PULL_SPEED 8
+#define BARNACLE_KILL_VICTIM_DELAY 5 // how many seconds after pulling prey in to gib them.
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define BARNACLE_AE_PUKEGIB 2
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CNPC_Barnacle : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Barnacle, CHL1BaseNPC );
+
+public:
+ void Spawn( void );
+ void InitTonguePosition( void );
+ void Precache( void );
+ CBaseEntity* TongueTouchEnt ( float *pflLength );
+ Class_T Classify ( void );
+ void HandleAnimEvent( animevent_t *pEvent );
+ void BarnacleThink ( void );
+ void WaitTillDead ( void );
+ void Event_Killed( const CTakeDamageInfo &info );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+ DECLARE_DATADESC();
+
+ float m_flAltitude;
+
+ float m_flKillVictimTime;
+ int m_cGibs;// barnacle loads up on gibs each time it kills something.
+ bool m_fLiftingPrey;
+ float m_flTongueAdj;
+ float m_flIgnoreTouchesUntil;
+
+public:
+ DEFINE_CUSTOM_AI;
+};
+
+#endif //HL1_NPC_BARNACLE_H
diff --git a/game/server/hl1/hl1_npc_barney.cpp b/game/server/hl1/hl1_npc_barney.cpp
new file mode 100644
index 0000000..1ff032c
--- /dev/null
+++ b/game/server/hl1/hl1_npc_barney.cpp
@@ -0,0 +1,938 @@
+//========= 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 "hl1_npc_barney.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 "ai_behavior_follow.h"
+#include "AI_Criteria.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+
+#define BA_ATTACK "BA_ATTACK"
+#define BA_MAD "BA_MAD"
+#define BA_SHOT "BA_SHOT"
+#define BA_KILL "BA_KILL"
+#define BA_POK "BA_POK"
+
+ConVar sk_barney_health( "sk_barney_health","35");
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+// first flag is barney dying for scripted sequences?
+#define BARNEY_AE_DRAW ( 2 )
+#define BARNEY_AE_SHOOT ( 3 )
+#define BARNEY_AE_HOLSTER ( 4 )
+
+#define BARNEY_BODY_GUNHOLSTERED 0
+#define BARNEY_BODY_GUNDRAWN 1
+#define BARNEY_BODY_GUNGONE 2
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_Barney )
+ DEFINE_FIELD( m_fGunDrawn, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flPainTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flCheckAttackTime, FIELD_TIME ),
+ DEFINE_FIELD( m_fLastAttackCheck, FIELD_BOOLEAN ),
+
+ DEFINE_THINKFUNC( SUB_LVFadeOut ),
+
+ //DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS( monster_barney, CNPC_Barney );
+
+
+static BOOL IsFacing( CBaseEntity *pevTest, const Vector &reference )
+{
+ Vector vecDir = (reference - pevTest->GetAbsOrigin());
+ vecDir.z = 0;
+ VectorNormalize( vecDir );
+ Vector forward;
+ QAngle angle;
+ angle = pevTest->GetAbsAngles();
+ angle.x = 0;
+ AngleVectors( angle, &forward );
+ // He's facing me, he meant it
+ if ( DotProduct( forward, vecDir ) > 0.96 ) // +/- 15 degrees or so
+ {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Barney::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/barney.mdl");
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_RED;
+ m_iHealth = sk_barney_health.GetFloat();
+ SetViewOffset( Vector ( 0, 0, 100 ) );// position of the eyes relative to monster's origin.
+ m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello
+ m_NPCState = NPC_STATE_NONE;
+
+ SetBodygroup( 1, 0 );
+
+ m_fGunDrawn = false;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE | bits_CAP_DOORS_GROUP);
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE );
+
+ NPCInit();
+
+ SetUse( &CNPC_Barney::FollowerUse );
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Barney::Precache()
+{
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ PrecacheModel("models/barney.mdl");
+
+ PrecacheScriptSound( "Barney.FirePistol" );
+ PrecacheScriptSound( "Barney.Pain" );
+ PrecacheScriptSound( "Barney.Die" );
+
+ // every new barney must call this, otherwise
+ // when a level is loaded, nobody will talk (time is reset to 0)
+ TalkInit();
+ BaseClass::Precache();
+}
+
+void CNPC_Barney::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet )
+{
+ BaseClass::ModifyOrAppendCriteria( criteriaSet );
+
+ bool predisaster = FBitSet( m_spawnflags, SF_NPC_PREDISASTER ) ? true : false;
+
+ criteriaSet.AppendCriteria( "disaster", predisaster ? "[disaster::pre]" : "[disaster::post]" );
+}
+
+// Init talk data
+void CNPC_Barney::TalkInit()
+{
+ BaseClass::TalkInit();
+
+ // get voice for head - just one barney voice for now
+ GetExpresser()->SetVoicePitch( 100 );
+}
+
+
+//=========================================================
+// GetSoundInterests - returns a bit mask indicating which types
+// of sounds this monster regards.
+//=========================================================
+int CNPC_Barney::GetSoundInterests ( void)
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_CARCASS |
+ SOUND_MEAT |
+ SOUND_GARBAGE |
+ SOUND_DANGER |
+ SOUND_PLAYER;
+}
+
+//=========================================================
+// Classify - indicates this monster's place in the
+// relationship table.
+//=========================================================
+Class_T CNPC_Barney::Classify ( void )
+{
+ return CLASS_PLAYER_ALLY;
+}
+
+//=========================================================
+// ALertSound - barney says "Freeze!"
+//=========================================================
+void CNPC_Barney::AlertSound( void )
+{
+ if ( GetEnemy() != NULL )
+ {
+ if ( IsOkToSpeak() )
+ {
+ Speak( BA_ATTACK );
+ }
+ }
+
+}
+//=========================================================
+// SetYawSpeed - allows each sequence to have a different
+// turn rate associated with it.
+//=========================================================
+void CNPC_Barney::SetYawSpeed ( void )
+{
+ int ys;
+
+ ys = 0;
+
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ ys = 70;
+ break;
+ case ACT_WALK:
+ ys = 70;
+ break;
+ case ACT_RUN:
+ ys = 90;
+ break;
+ default:
+ ys = 70;
+ break;
+ }
+
+ GetMotor()->SetYawSpeed( ys );
+}
+
+//=========================================================
+// CheckRangeAttack1
+//=========================================================
+bool CNPC_Barney::CheckRangeAttack1 ( float flDot, float flDist )
+{
+ if ( gpGlobals->curtime > m_flCheckAttackTime )
+ {
+ trace_t tr;
+
+ Vector shootOrigin = GetAbsOrigin() + Vector( 0, 0, 55 );
+ CBaseEntity *pEnemy = GetEnemy();
+ Vector shootTarget = ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->GetAbsOrigin()) + GetEnemyLKP() );
+
+ UTIL_TraceLine ( shootOrigin, shootTarget, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+ m_flCheckAttackTime = gpGlobals->curtime + 1;
+ if ( tr.fraction == 1.0 || ( tr.m_pEnt != NULL && tr.m_pEnt == pEnemy) )
+ m_fLastAttackCheck = TRUE;
+ else
+ m_fLastAttackCheck = FALSE;
+
+ m_flCheckAttackTime = gpGlobals->curtime + 1.5;
+ }
+
+ return m_fLastAttackCheck;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : For innate range attack
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+int CNPC_Barney::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if (GetEnemy() == NULL)
+ {
+ return( COND_NONE );
+ }
+
+ else if ( flDist > 1024 )
+ {
+ return( COND_TOO_FAR_TO_ATTACK );
+ }
+ else if ( flDot < 0.5 )
+ {
+ return( COND_NOT_FACING_ATTACK );
+ }
+
+ if ( CheckRangeAttack1 ( flDot, flDist ) )
+ return( COND_CAN_RANGE_ATTACK1 );
+
+ return COND_NONE;
+}
+
+
+//=========================================================
+// BarneyFirePistol - shoots one round from the pistol at
+// the enemy barney is facing.
+//=========================================================
+void CNPC_Barney::BarneyFirePistol ( void )
+{
+ Vector vecShootOrigin;
+
+ vecShootOrigin = GetAbsOrigin() + Vector( 0, 0, 55 );
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin );
+
+ QAngle angDir;
+
+ VectorAngles( vecShootDir, angDir );
+// SetBlending( 0, angDir.x );
+ DoMuzzleFlash();
+
+ FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, m_iAmmoType );
+
+ int pitchShift = random->RandomInt( 0, 20 );
+
+ // Only shift about half the time
+ if ( pitchShift > 10 )
+ pitchShift = 0;
+ else
+ pitchShift -= 5;
+
+ CPASAttenuationFilter filter( this );
+
+ EmitSound_t params;
+ params.m_pSoundName = "Barney.FirePistol";
+ params.m_flVolume = 1;
+ params.m_nChannel= CHAN_WEAPON;
+ params.m_SoundLevel = SNDLVL_NORM;
+ params.m_nPitch = 100 + pitchShift;
+ EmitSound( filter, entindex(), params );
+
+ CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), 384, 0.3 );
+
+ // UNDONE: Reload?
+ m_cAmmoLoaded--;// take away a bullet!
+}
+
+int CNPC_Barney::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ // make sure friends talk about it if player hurts talkmonsters...
+ int ret = BaseClass::OnTakeDamage_Alive( inputInfo );
+
+ if ( !IsAlive() || m_lifeState == LIFE_DYING )
+ return ret;
+
+ if ( m_NPCState != NPC_STATE_PRONE && ( inputInfo.GetAttacker()->GetFlags() & FL_CLIENT ) )
+ {
+ // This is a heurstic to determine if the player intended to harm me
+ // If I have an enemy, we can't establish intent (may just be crossfire)
+ if ( GetEnemy() == NULL )
+ {
+ // If the player was facing directly at me, or I'm already suspicious, get mad
+ if ( HasMemory( bits_MEMORY_SUSPICIOUS ) || IsFacing( inputInfo.GetAttacker(), GetAbsOrigin() ) )
+ {
+ // Alright, now I'm pissed!
+ Speak( BA_MAD );
+
+ Remember( bits_MEMORY_PROVOKED );
+ StopFollowing();
+ }
+ else
+ {
+ // Hey, be careful with that
+ Speak( BA_SHOT );
+ Remember( bits_MEMORY_SUSPICIOUS );
+ }
+ }
+ else if ( !(GetEnemy()->IsPlayer()) && m_lifeState == LIFE_ALIVE )
+ {
+ Speak( BA_SHOT );
+ }
+ }
+
+ return ret;
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_Barney::PainSound( const CTakeDamageInfo &info )
+{
+ if (gpGlobals->curtime < m_flPainTime)
+ return;
+
+ m_flPainTime = gpGlobals->curtime + random->RandomFloat( 0.5, 0.75 );
+
+ CPASAttenuationFilter filter( this );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "Barney.Pain", params, NULL ) )
+ {
+ params.pitch = GetExpresser()->GetVoicePitch();
+
+ EmitSound_t ep( params );
+
+ EmitSound( filter, entindex(), ep );
+ }
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_Barney::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "Barney.Die", params, NULL ) )
+ {
+ params.pitch = GetExpresser()->GetVoicePitch();
+
+ EmitSound_t ep( params );
+
+ EmitSound( filter, entindex(), ep );
+ }
+}
+
+void CNPC_Barney::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ switch( ptr->hitgroup )
+ {
+ case HITGROUP_CHEST:
+ case HITGROUP_STOMACH:
+ if ( info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_BLAST) )
+ {
+ info.ScaleDamage( 0.5f );
+ }
+ break;
+ case 10:
+ if ( info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_CLUB) )
+ {
+ info.SetDamage( info.GetDamage() - 20 );
+ if ( info.GetDamage() <= 0 )
+ {
+ g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal );
+ info.SetDamage( 0.01 );
+ }
+ }
+ // always a head shot
+ ptr->hitgroup = HITGROUP_HEAD;
+ break;
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+void CNPC_Barney::Event_Killed( const CTakeDamageInfo &info )
+{
+ if ( m_nBody < BARNEY_BODY_GUNGONE )
+ {
+ // drop the gun!
+ Vector vecGunPos;
+ QAngle angGunAngles;
+ CBaseEntity *pGun = NULL;
+
+ SetBodygroup( 1, BARNEY_BODY_GUNGONE);
+
+ GetAttachment( "0", vecGunPos, angGunAngles );
+
+ angGunAngles.y += 180;
+ pGun = DropItem( "weapon_glock", vecGunPos, angGunAngles );
+ }
+
+ SetUse( NULL );
+ BaseClass::Event_Killed( info );
+
+ if ( UTIL_IsLowViolence() )
+ {
+ SUB_StartLVFadeOut( 0.0f );
+ }
+}
+
+void CNPC_Barney::SUB_StartLVFadeOut( float delay, bool notSolid )
+{
+ SetThink( &CNPC_Barney::SUB_LVFadeOut );
+ SetNextThink( gpGlobals->curtime + delay );
+ SetRenderColorA( 255 );
+ m_nRenderMode = kRenderNormal;
+
+ if ( notSolid )
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetLocalAngularVelocity( vec3_angle );
+ }
+}
+
+void CNPC_Barney::SUB_LVFadeOut( void )
+{
+ if( VPhysicsGetObject() )
+ {
+ if( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD || GetEFlags() & EFL_IS_BEING_LIFTED_BY_BARNACLE )
+ {
+ // Try again in a few seconds.
+ SetNextThink( gpGlobals->curtime + 5 );
+ SetRenderColorA( 255 );
+ return;
+ }
+ }
+
+ float dt = gpGlobals->frametime;
+ if ( dt > 0.1f )
+ {
+ dt = 0.1f;
+ }
+ m_nRenderMode = kRenderTransTexture;
+ int speed = MAX(3,256*dt); // fade out over 3 seconds
+ SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) );
+ NetworkStateChanged();
+
+ if ( m_clrRender->a == 0 )
+ {
+ UTIL_Remove(this);
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime );
+ }
+}
+
+void CNPC_Barney::StartTask( const Task_t *pTask )
+{
+ BaseClass::StartTask( pTask );
+}
+
+void CNPC_Barney::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_RANGE_ATTACK1:
+ if (GetEnemy() != NULL && (GetEnemy()->IsPlayer()))
+ {
+ m_flPlaybackRate = 1.5;
+ }
+ BaseClass::RunTask( pTask );
+ break;
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//
+// Returns number of events handled, 0 if none.
+//=========================================================
+void CNPC_Barney::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case BARNEY_AE_SHOOT:
+ BarneyFirePistol();
+ break;
+
+ case BARNEY_AE_DRAW:
+ // barney's bodygroup switches here so he can pull gun from holster
+ SetBodygroup( 1, BARNEY_BODY_GUNDRAWN);
+ m_fGunDrawn = true;
+ break;
+
+ case BARNEY_AE_HOLSTER:
+ // change bodygroup to replace gun in holster
+ SetBodygroup( 1, BARNEY_BODY_GUNHOLSTERED);
+ m_fGunDrawn = false;
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ }
+}
+
+
+//=========================================================
+// AI Schedules Specific to this monster
+//=========================================================
+
+int CNPC_Barney::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_ARM_WEAPON:
+ if ( GetEnemy() != NULL )
+ {
+ // face enemy, then draw.
+ return SCHED_BARNEY_ENEMY_DRAW;
+ }
+ break;
+
+ // Hook these to make a looping schedule
+ case SCHED_TARGET_FACE:
+ {
+ int baseType;
+
+ // call base class default so that scientist will talk
+ // when 'used'
+ baseType = BaseClass::TranslateSchedule( scheduleType );
+
+ if ( baseType == SCHED_IDLE_STAND )
+ return SCHED_BARNEY_FACE_TARGET;
+ else
+ return baseType;
+ }
+ break;
+
+ case SCHED_TARGET_CHASE:
+ {
+ return SCHED_BARNEY_FOLLOW;
+ break;
+ }
+
+ case SCHED_IDLE_STAND:
+ {
+ int baseType;
+
+ // call base class default so that scientist will talk
+ // when 'used'
+ baseType = BaseClass::TranslateSchedule( scheduleType );
+
+ if ( baseType == SCHED_IDLE_STAND )
+ return SCHED_BARNEY_IDLE_STAND;
+ else
+ return baseType;
+ }
+ break;
+
+ case SCHED_TAKE_COVER_FROM_ENEMY:
+ case SCHED_CHASE_ENEMY:
+ {
+ if ( HasCondition( COND_HEAVY_DAMAGE ) )
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+
+ // No need to take cover since I can see him
+ // SHOOT!
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && m_fGunDrawn )
+ return SCHED_RANGE_ATTACK1;
+ }
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//=========================================================
+// SelectSchedule - Decides which type of schedule best suits
+// the monster's current state and conditions. Then calls
+// monster's member function to get a pointer to a schedule
+// of the proper type.
+//=========================================================
+int CNPC_Barney::SelectSchedule( void )
+{
+ if ( m_NPCState == NPC_STATE_COMBAT || GetEnemy() != NULL )
+ {
+ // Priority action!
+ if (!m_fGunDrawn )
+ return SCHED_ARM_WEAPON;
+ }
+
+ if ( GetFollowTarget() == NULL )
+ {
+ if ( HasCondition( COND_PLAYER_PUSHING ) && !(GetSpawnFlags() & SF_NPC_PREDISASTER ) ) // Player wants me to move
+ return SCHED_HL1TALKER_FOLLOW_MOVE_AWAY;
+ }
+
+ if ( BehaviorSelectSchedule() )
+ return BaseClass::SelectSchedule();
+
+ if ( HasCondition( COND_HEAR_DANGER ) )
+ {
+ CSound *pSound;
+ pSound = GetBestSound();
+
+ ASSERT( pSound != NULL );
+
+ if ( pSound && pSound->IsSoundType( SOUND_DANGER ) )
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+ }
+ if ( HasCondition( COND_ENEMY_DEAD ) && IsOkToSpeak() )
+ {
+ Speak( BA_KILL );
+ }
+
+ switch( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ {
+ // dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ return BaseClass::SelectSchedule(); // call base class, all code to handle dead enemies is centralized there.
+
+ // always act surprized with a new enemy
+ if ( HasCondition( COND_NEW_ENEMY ) && HasCondition( COND_LIGHT_DAMAGE) )
+ return SCHED_SMALL_FLINCH;
+
+ if ( HasCondition( COND_HEAVY_DAMAGE ) )
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+
+ if ( !HasCondition(COND_SEE_ENEMY) )
+ {
+ // we can't see the enemy
+ if ( !HasCondition(COND_ENEMY_OCCLUDED) )
+ {
+ // enemy is unseen, but not occluded!
+ // turn to face enemy
+ return SCHED_COMBAT_FACE;
+ }
+ else
+ {
+ return SCHED_CHASE_ENEMY;
+ }
+ }
+ }
+ break;
+
+ case NPC_STATE_ALERT:
+ case NPC_STATE_IDLE:
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ // flinch if hurt
+ return SCHED_SMALL_FLINCH;
+ }
+
+ if ( GetEnemy() == NULL && GetFollowTarget() )
+ {
+ if ( !GetFollowTarget()->IsAlive() )
+ {
+ // UNDONE: Comment about the recently dead player here?
+ StopFollowing();
+ break;
+ }
+ else
+ {
+
+ return SCHED_TARGET_FACE;
+ }
+ }
+
+ // try to say something about smells
+ TrySmellTalk();
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+NPC_STATE CNPC_Barney::SelectIdealState ( void )
+{
+ return BaseClass::SelectIdealState();
+}
+
+void CNPC_Barney::DeclineFollowing( void )
+{
+ if ( CanSpeakAfterMyself() )
+ {
+ Speak( BA_POK );
+ }
+}
+
+bool CNPC_Barney::CanBecomeRagdoll( void )
+{
+ if ( UTIL_IsLowViolence() )
+ {
+ return false;
+ }
+
+ return BaseClass::CanBecomeRagdoll();
+}
+
+bool CNPC_Barney::ShouldGib( const CTakeDamageInfo &info )
+{
+ if ( UTIL_IsLowViolence() )
+ {
+ return false;
+ }
+
+ return BaseClass::ShouldGib( info );
+}
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( monster_barney, CNPC_Barney )
+
+ //=========================================================
+ // > SCHED_BARNEY_FOLLOW
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_BARNEY_FOLLOW,
+
+ " Tasks"
+// " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_BARNEY_STOP_FOLLOWING"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_MOVE_TO_TARGET_RANGE 180"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_TARGET_FACE"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_PROVOKED"
+ )
+
+ //=========================================================
+ // > SCHED_BARNEY_ENEMY_DRAW
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_BARNEY_ENEMY_DRAW,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_ARM"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_BARNEY_FACE_TARGET
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_BARNEY_FACE_TARGET,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_TARGET 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_BARNEY_FOLLOW"
+ " "
+ " Interrupts"
+ " COND_GIVE_WAY"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_BARNEY_IDLE_STAND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_BARNEY_IDLE_STAND,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2"
+ " TASK_TALKER_HEADRESET 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ " COND_HEAR_COMBAT"
+ " COND_SMELL"
+ )
+
+AI_END_CUSTOM_NPC()
+
+
+//=========================================================
+// DEAD BARNEY PROP
+//
+// Designer selects a pose in worldcraft, 0 through num_poses-1
+// this value is added to what is selected as the 'first dead pose'
+// among the monster's normal animations. All dead poses must
+// appear sequentially in the model file. Be sure and set
+// the m_iFirstPose properly!
+//
+//=========================================================
+class CNPC_DeadBarney : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_DeadBarney, CAI_BaseNPC );
+public:
+
+ void Spawn( void );
+ Class_T Classify ( void ) { return CLASS_NONE; }
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ float MaxYawSpeed ( void ) { return 8.0f; }
+
+ int m_iPose;// which sequence to display -- temporary, don't need to save
+ int m_iDesiredSequence;
+ static char *m_szPoses[3];
+
+ DECLARE_DATADESC();
+};
+
+char *CNPC_DeadBarney::m_szPoses[] = { "lying_on_back", "lying_on_side", "lying_on_stomach" };
+
+bool CNPC_DeadBarney::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "pose" ) )
+ m_iPose = atoi( szValue );
+ else
+ BaseClass::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+LINK_ENTITY_TO_CLASS( monster_barney_dead, CNPC_DeadBarney );
+
+BEGIN_DATADESC( CNPC_DeadBarney )
+END_DATADESC()
+
+//=========================================================
+// ********** DeadBarney SPAWN **********
+//=========================================================
+void CNPC_DeadBarney::Spawn( void )
+{
+ PrecacheModel("models/barney.mdl");
+ SetModel( "models/barney.mdl");
+
+ ClearEffects();
+ SetSequence( 0 );
+ m_bloodColor = BLOOD_COLOR_RED;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ SetSequence( m_iDesiredSequence = LookupSequence( m_szPoses[m_iPose] ) );
+ if ( GetSequence() == -1 )
+ {
+ Msg ( "Dead barney with bad pose\n" );
+ }
+ // Corpses have less health
+ m_iHealth = 0.0;//gSkillData.barneyHealth;
+
+ NPCInitDead();
+}
diff --git a/game/server/hl1/hl1_npc_barney.h b/game/server/hl1/hl1_npc_barney.h
new file mode 100644
index 0000000..442fb02
--- /dev/null
+++ b/game/server/hl1/hl1_npc_barney.h
@@ -0,0 +1,81 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_BARNEY_H
+#define NPC_BARNEY_H
+
+#include "hl1_npc_talker.h"
+
+//=========================================================
+//=========================================================
+class CNPC_Barney : public CHL1NPCTalker
+{
+ DECLARE_CLASS( CNPC_Barney, CHL1NPCTalker );
+public:
+
+ DECLARE_DATADESC();
+
+ virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set );
+
+ void Precache( void );
+ void Spawn( void );
+ void TalkInit( void );
+
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+
+ int GetSoundInterests ( void );
+ Class_T Classify ( void );
+ void AlertSound( void );
+ void SetYawSpeed ( void );
+
+ bool CheckRangeAttack1 ( float flDot, float flDist );
+ void BarneyFirePistol ( void );
+
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ void PainSound( const CTakeDamageInfo &info );
+ void DeathSound( const CTakeDamageInfo &info );
+
+ void HandleAnimEvent( animevent_t *pEvent );
+ int TranslateSchedule( int scheduleType );
+ int SelectSchedule( void );
+
+ void DeclineFollowing( void );
+
+ bool CanBecomeRagdoll( void );
+ bool ShouldGib( const CTakeDamageInfo &info );
+
+ int RangeAttack1Conditions( float flDot, float flDist );
+
+ void SUB_StartLVFadeOut( float delay = 10.0f, bool bNotSolid = true );
+ void SUB_LVFadeOut( void );
+
+ NPC_STATE SelectIdealState ( void );
+
+ bool m_fGunDrawn;
+ float m_flPainTime;
+ float m_flCheckAttackTime;
+ bool m_fLastAttackCheck;
+
+ int m_iAmmoType;
+
+ enum
+ {
+ SCHED_BARNEY_FOLLOW = BaseClass::NEXT_SCHEDULE,
+ SCHED_BARNEY_ENEMY_DRAW,
+ SCHED_BARNEY_FACE_TARGET,
+ SCHED_BARNEY_IDLE_STAND,
+ SCHED_BARNEY_STOP_FOLLOWING,
+ };
+
+ DEFINE_CUSTOM_AI;
+};
+
+#endif //NPC_BARNEY_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_bigmomma.cpp b/game/server/hl1/hl1_npc_bigmomma.cpp
new file mode 100644
index 0000000..c769375
--- /dev/null
+++ b/game/server/hl1/hl1_npc_bigmomma.cpp
@@ -0,0 +1,1313 @@
+//========= 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 "hl1_ai_basenpc.h"
+#include "ai_navigator.h"
+#include "decals.h"
+#include "effect_dispatch_data.h"
+#include "te_effect_dispatch.h"
+#include "Sprite.h"
+
+ConVar sk_bigmomma_health_factor( "sk_bigmomma_health_factor", "1" );
+ConVar sk_bigmomma_dmg_slash( "sk_bigmomma_dmg_slash", "50" );
+ConVar sk_bigmomma_dmg_blast( "sk_bigmomma_dmg_blast", "100" );
+ConVar sk_bigmomma_radius_blast( "sk_bigmomma_radius_blast", "250" );
+
+float GetCurrentGravity( void );
+
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define BIG_AE_STEP1 1 // Footstep left
+#define BIG_AE_STEP2 2 // Footstep right
+#define BIG_AE_STEP3 3 // Footstep back left
+#define BIG_AE_STEP4 4 // Footstep back right
+#define BIG_AE_SACK 5 // Sack slosh
+#define BIG_AE_DEATHSOUND 6 // Death sound
+
+#define BIG_AE_MELEE_ATTACKBR 8 // Leg attack
+#define BIG_AE_MELEE_ATTACKBL 9 // Leg attack
+#define BIG_AE_MELEE_ATTACK1 10 // Leg attack
+#define BIG_AE_MORTAR_ATTACK1 11 // Launch a mortar
+#define BIG_AE_LAY_CRAB 12 // Lay a headcrab
+#define BIG_AE_JUMP_FORWARD 13 // Jump up and forward
+#define BIG_AE_SCREAM 14 // alert sound
+#define BIG_AE_PAIN_SOUND 15 // pain sound
+#define BIG_AE_ATTACK_SOUND 16 // attack sound
+#define BIG_AE_BIRTH_SOUND 17 // birth sound
+#define BIG_AE_EARLY_TARGET 50 // Fire target early
+
+
+enum
+{
+ SCHED_NODE_FAIL = LAST_SHARED_SCHEDULE,
+ SCHED_BIG_NODE,
+};
+
+enum
+{
+ TASK_MOVE_TO_NODE_RANGE = LAST_SHARED_TASK, // Move within node range
+ TASK_FIND_NODE, // Find my next node
+ TASK_PLAY_NODE_PRESEQUENCE, // Play node pre-script
+ TASK_PLAY_NODE_SEQUENCE, // Play node script
+ TASK_PROCESS_NODE, // Fire targets, etc.
+ TASK_WAIT_NODE, // Wait at the node
+ TASK_NODE_DELAY, // Delay walking toward node for a bit. You've failed to get there
+ TASK_NODE_YAW, // Get the best facing direction for this node
+ TASK_CHECK_NODE_PROXIMITY,
+};
+
+// User defined conditions
+//#define bits_COND_NODE_SEQUENCE ( COND_SPECIAL1 ) // pev->netname contains the name of a sequence to play
+
+// Attack distance constants
+#define BIG_ATTACKDIST 170
+#define BIG_MORTARDIST 800
+#define BIG_MAXCHILDREN 6 // Max # of live headcrab children
+
+
+#define bits_MEMORY_CHILDPAIR (bits_MEMORY_CUSTOM1)
+#define bits_MEMORY_ADVANCE_NODE (bits_MEMORY_CUSTOM2)
+#define bits_MEMORY_COMPLETED_NODE (bits_MEMORY_CUSTOM3)
+#define bits_MEMORY_FIRED_NODE (bits_MEMORY_CUSTOM4)
+
+int gSpitSprite, gSpitDebrisSprite;
+
+
+Vector VecCheckSplatToss( CBaseEntity *pEntity, const Vector &vecSpot1, Vector vecSpot2, float maxHeight );
+void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count );
+
+#define SF_INFOBM_RUN 0x0001
+#define SF_INFOBM_WAIT 0x0002
+
+//=========================================================
+// Mortar shot entity
+//=========================================================
+class CBMortar : public CBaseAnimating
+{
+ DECLARE_CLASS( CBMortar, CBaseAnimating );
+public:
+ void Spawn( void );
+
+ virtual void Precache();
+
+ static CBMortar *Shoot( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity );
+ void Touch( CBaseEntity *pOther );
+ void Animate( void );
+
+ float m_flDmgTime;
+
+ DECLARE_DATADESC();
+
+ int m_maxFrame;
+ int m_iFrame;
+
+ CSprite* pSprite;
+};
+
+LINK_ENTITY_TO_CLASS( bmortar, CBMortar );
+
+BEGIN_DATADESC( CBMortar )
+ DEFINE_FIELD( m_maxFrame, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flDmgTime, FIELD_FLOAT ),
+ DEFINE_FUNCTION( Animate ),
+ DEFINE_FIELD( m_iFrame, FIELD_INTEGER ),
+ DEFINE_FIELD( pSprite, FIELD_CLASSPTR ),
+END_DATADESC()
+
+
+// AI Nodes for Big Momma
+class CInfoBM : public CPointEntity
+{
+ DECLARE_CLASS( CInfoBM, CPointEntity );
+public:
+ void Spawn( void );
+ bool KeyValue( const char *szKeyName, const char *szValue );
+
+ // name in pev->targetname
+ // next in pev->target
+ // radius in pev->scale
+ // health in pev->health
+ // Reach target in pev->message
+ // Reach delay in pev->speed
+ // Reach sequence in pev->netname
+
+ DECLARE_DATADESC();
+
+ float m_flRadius;
+ float m_flDelay;
+ string_t m_iszReachTarget;
+ string_t m_iszReachSequence;
+ string_t m_iszPreSequence;
+
+ COutputEvent m_OnAnimationEvent;
+};
+
+LINK_ENTITY_TO_CLASS( info_bigmomma, CInfoBM );
+
+BEGIN_DATADESC( CInfoBM )
+ DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flDelay, FIELD_FLOAT ),
+ DEFINE_KEYFIELD( m_iszReachTarget, FIELD_STRING, "reachtarget" ),
+ DEFINE_KEYFIELD( m_iszReachSequence, FIELD_STRING, "reachsequence" ),
+ DEFINE_KEYFIELD( m_iszPreSequence, FIELD_STRING, "presequence" ),
+ DEFINE_OUTPUT( m_OnAnimationEvent, "OnAnimationEvent" ),
+END_DATADESC()
+
+
+void CInfoBM::Spawn( void )
+{
+ BaseClass::Spawn();
+
+// Msg( "Name %s\n", STRING( GetEntityName() ) );
+}
+
+bool CInfoBM::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if (FStrEq( szKeyName, "radius"))
+ {
+ m_flRadius = atof( szValue );
+ return true;
+ }
+ else if (FStrEq( szKeyName, "reachdelay" ))
+ {
+ m_flDelay = atof( szValue);
+ return true;
+ }
+ else if (FStrEq( szKeyName, "health" ))
+ {
+ m_iHealth = atoi( szValue );
+ return true;
+ }
+
+ return BaseClass::KeyValue(szKeyName, szValue );
+}
+
+// UNDONE:
+//
+#define BIG_CHILDCLASS "monster_babycrab"
+
+
+class CNPC_BigMomma : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_BigMomma, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+
+ Class_T Classify( void ) { return CLASS_ALIEN_MONSTER; };
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ int OnTakeDamage( const CTakeDamageInfo &info );
+ void HandleAnimEvent( animevent_t *pEvent );
+ void LayHeadcrab( void );
+ void LaunchMortar( void );
+ void DeathNotice( CBaseEntity *pevChild );
+
+ int MeleeAttack1Conditions( float flDot, float flDist ); // Slash
+ int MeleeAttack2Conditions( float flDot, float flDist ); // Lay a crab
+ int RangeAttack1Conditions( float flDot, float flDist ); // Mortar launch
+
+
+ BOOL CanLayCrab( void )
+ {
+ if ( m_crabTime < gpGlobals->curtime && m_crabCount < BIG_MAXCHILDREN )
+ {
+ // Don't spawn crabs inside each other
+ Vector mins = GetAbsOrigin() - Vector( 32, 32, 0 );
+ Vector maxs = GetAbsOrigin() + Vector( 32, 32, 0 );
+
+ CBaseEntity *pList[2];
+ int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_NPC );
+ for ( int i = 0; i < count; i++ )
+ {
+ if ( pList[i] != this ) // Don't hurt yourself!
+ return COND_NONE;
+ }
+ return COND_CAN_MELEE_ATTACK2;
+ }
+
+ return COND_NONE;
+ }
+
+ void Activate ( void );
+
+ void NodeReach( void );
+ void NodeStart( string_t iszNextNode );
+ bool ShouldGoToNode( void );
+
+ const char *GetNodeSequence( void )
+ {
+ CInfoBM *pTarget = (CInfoBM*)GetTarget();
+
+ if ( pTarget && FClassnameIs( pTarget, "info_bigmomma" ) )
+ {
+ return STRING( pTarget->m_iszReachSequence ); // netname holds node sequence
+ }
+
+ return NULL;
+ }
+
+
+ const char *GetNodePresequence( void )
+ {
+ CInfoBM *pTarget = (CInfoBM *)GetTarget();
+
+ if ( pTarget && FClassnameIs( pTarget, "info_bigmomma" ) )
+ {
+ return STRING( pTarget->m_iszPreSequence );
+ }
+ return NULL;
+ }
+
+ float GetNodeDelay( void )
+ {
+ CInfoBM *pTarget = (CInfoBM *)GetTarget();
+
+ if ( pTarget && FClassnameIs( pTarget, "info_bigmomma" ) )
+ {
+ return pTarget->m_flDelay; // Speed holds node delay
+ }
+ return 0;
+ }
+
+ float GetNodeRange( void )
+ {
+ CInfoBM *pTarget = (CInfoBM *)GetTarget();
+
+ if ( pTarget && FClassnameIs( pTarget, "info_bigmomma" ) )
+ {
+ return pTarget->m_flRadius; // Scale holds node delay
+ }
+
+ return 1e6;
+ }
+
+ float GetNodeYaw( void )
+ {
+ CBaseEntity *pTarget = GetTarget();
+
+ if ( pTarget )
+ {
+ if ( pTarget->GetAbsAngles().y != 0 )
+ return pTarget->GetAbsAngles().y;
+ }
+
+ return GetAbsAngles().y;
+ }
+
+ // Restart the crab count on each new level
+ void OnRestore( void )
+ {
+ BaseClass::OnRestore();
+ m_crabCount = 0;
+ }
+
+ int SelectSchedule( void );
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+
+ float MaxYawSpeed( void );
+
+ DECLARE_DATADESC();
+
+ DEFINE_CUSTOM_AI;
+
+ /*
+ void RunTask( Task_t *pTask );
+ void StartTask( Task_t *pTask );
+ Schedule_t *GetSchedule( void );
+ Schedule_t *GetScheduleOfType( int Type );
+ void SetYawSpeed( void );
+
+ CUSTOM_SCHEDULES;
+*/
+
+private:
+ float m_nodeTime;
+ float m_crabTime;
+ float m_mortarTime;
+ float m_painSoundTime;
+ int m_crabCount;
+ float m_flDmgTime;
+
+ bool m_bDoneWithPath;
+
+ string_t m_iszTarget;
+ string_t m_iszNetName;
+ float m_flWait;
+
+ Vector m_vTossDir;
+
+
+};
+
+
+BEGIN_DATADESC( CNPC_BigMomma )
+ DEFINE_FIELD( m_nodeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_crabTime, FIELD_TIME ),
+ DEFINE_FIELD( m_mortarTime, FIELD_TIME ),
+ DEFINE_FIELD( m_painSoundTime, FIELD_TIME ),
+ DEFINE_KEYFIELD( m_iszNetName, FIELD_STRING, "netname" ),
+ DEFINE_FIELD( m_flWait, FIELD_TIME ),
+ DEFINE_FIELD( m_iszTarget, FIELD_STRING ),
+
+ DEFINE_FIELD( m_crabCount, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flDmgTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bDoneWithPath, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vTossDir, FIELD_VECTOR ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS ( monster_bigmomma, CNPC_BigMomma );
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_BigMomma::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/big_mom.mdl" );
+ UTIL_SetSize( this, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) );
+
+ Vector vecSurroundingMins( -95, -95, 0 );
+ Vector vecSurroundingMaxs( 95, 95, 190 );
+ CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );
+
+ SetNavType( NAV_GROUND );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ m_iHealth = 150 * sk_bigmomma_health_factor.GetFloat();
+
+ SetHullType( HULL_WIDE_HUMAN );
+ SetHullSizeNormal();
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND );
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 );
+
+// pev->view_ofs = Vector ( 0, 0, 128 );// position of the eyes relative to monster's origin.
+ m_flFieldOfView = 0.3;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ m_bDoneWithPath = false;
+
+ m_nodeTime = 0.0f;
+
+ m_iszTarget = m_iszNetName;
+
+ NPCInit();
+
+ BaseClass::Spawn();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_BigMomma::Precache()
+{
+ PrecacheModel("models/big_mom.mdl");
+
+ UTIL_PrecacheOther( BIG_CHILDCLASS );
+
+ // TEMP: Squid
+ PrecacheModel("sprites/mommaspit.vmt");// spit projectile.
+ gSpitSprite = PrecacheModel("sprites/mommaspout.vmt");// client side spittle.
+ gSpitDebrisSprite = PrecacheModel("sprites/mommablob.vmt" );
+
+ PrecacheScriptSound( "BigMomma.Pain" );
+ PrecacheScriptSound( "BigMomma.Attack" );
+ PrecacheScriptSound( "BigMomma.AttackHit" );
+ PrecacheScriptSound( "BigMomma.Alert" );
+ PrecacheScriptSound( "BigMomma.Birth" );
+ PrecacheScriptSound( "BigMomma.Sack" );
+ PrecacheScriptSound( "BigMomma.Die" );
+ PrecacheScriptSound( "BigMomma.FootstepLeft" );
+ PrecacheScriptSound( "BigMomma.FootstepRight" );
+ PrecacheScriptSound( "BigMomma.LayHeadcrab" );
+ PrecacheScriptSound( "BigMomma.ChildDie" );
+ PrecacheScriptSound( "BigMomma.LaunchMortar" );
+}
+
+//=========================================================
+// SetYawSpeed - allows each sequence to have a different
+// turn rate associated with it.
+//=========================================================
+float CNPC_BigMomma::MaxYawSpeed ( void )
+{
+ float ys = 90.0f;
+
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ ys = 100.0f;
+ break;
+ default:
+ ys = 90.0f;
+ }
+
+ return ys;
+}
+
+void CNPC_BigMomma::Activate( void )
+{
+ if ( GetTarget() == NULL )
+ Remember( bits_MEMORY_ADVANCE_NODE ); // Start 'er up
+
+ BaseClass::Activate();
+}
+
+void CNPC_BigMomma::NodeStart( string_t iszNextNode )
+{
+ m_iszTarget = iszNextNode;
+
+ const char *pTargetName = STRING( m_iszTarget );
+
+ CBaseEntity *pTarget = NULL;
+
+ if ( pTargetName )
+ pTarget = gEntList.FindEntityByName( NULL, pTargetName );
+
+ if ( pTarget == NULL )
+ {
+ //Msg( "BM: Finished the path!!\n" );
+ m_bDoneWithPath = true;
+ return;
+ }
+
+ SetTarget( pTarget );
+}
+
+
+void CNPC_BigMomma::NodeReach( void )
+{
+ CInfoBM *pTarget = (CInfoBM*)GetTarget();
+
+ Forget( bits_MEMORY_ADVANCE_NODE );
+
+ if ( !pTarget )
+ return;
+
+ if ( pTarget->m_iHealth >= 1 )
+ m_iMaxHealth = m_iHealth = pTarget->m_iHealth * sk_bigmomma_health_factor.GetFloat();
+
+ if ( !HasMemory( bits_MEMORY_FIRED_NODE ) )
+ {
+ if ( pTarget )
+ {
+ pTarget->m_OnAnimationEvent.FireOutput( this, this );
+ }
+ }
+
+ Forget( bits_MEMORY_FIRED_NODE );
+
+ m_iszTarget = pTarget->m_target;
+
+ if ( pTarget->m_iHealth == 0 )
+ Remember( bits_MEMORY_ADVANCE_NODE ); // Move on if no health at this node
+ else
+ {
+ GetNavigator()->ClearGoal();
+ }
+}
+
+
+void CNPC_BigMomma::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo dmgInfo = info;
+
+ if ( ptr->hitbox <= 9 )
+ {
+ // didn't hit the sack?
+ if ( m_flDmgTime != gpGlobals->curtime || (random->RandomInt( 0, 10 ) < 1) )
+ {
+ g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal );
+ m_flDmgTime = gpGlobals->curtime;
+ }
+
+ // don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
+ dmgInfo.SetDamage( 0.1 );
+ }
+ else
+ {
+ SpawnBlood( ptr->endpos + ptr->plane.normal * 15, vecDir, m_bloodColor, 100 );
+
+ if ( gpGlobals->curtime > m_painSoundTime )
+ {
+ m_painSoundTime = gpGlobals->curtime + random->RandomInt(1, 3);
+ EmitSound( "BigMomma.Pain" );
+ }
+ }
+
+ BaseClass::TraceAttack( dmgInfo, vecDir, ptr, pAccumulator );
+}
+
+
+int CNPC_BigMomma::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ CTakeDamageInfo newInfo = info;
+
+ // Don't take any acid damage -- BigMomma's mortar is acid
+ if ( newInfo.GetDamageType() & DMG_ACID )
+ {
+ newInfo.SetDamage( 0 );
+ }
+
+ // never die from damage, just advance to the next node
+ if ( ( GetHealth() - newInfo.GetDamage() ) < 1 )
+ {
+ newInfo.SetDamage( 0 );
+ Remember( bits_MEMORY_ADVANCE_NODE );
+ DevMsg( 2, "BM: Finished node health!!!\n" );
+ }
+
+ DevMsg( 2, "BM Health: %f\n", GetHealth() - newInfo.GetDamage() );
+
+ return BaseClass::OnTakeDamage( newInfo );
+}
+
+bool CNPC_BigMomma::ShouldGoToNode( void )
+{
+ if ( HasMemory( bits_MEMORY_ADVANCE_NODE ) )
+ {
+ if ( m_nodeTime < gpGlobals->curtime )
+ return true;
+ }
+ return false;
+}
+
+
+int CNPC_BigMomma::SelectSchedule( void )
+{
+ if ( ShouldGoToNode() )
+ {
+ return SCHED_BIG_NODE;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+
+void CNPC_BigMomma::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_CHECK_NODE_PROXIMITY:
+ {
+
+ }
+
+ break;
+ case TASK_FIND_NODE:
+ {
+ CBaseEntity *pTarget = GetTarget();
+
+ if ( !HasMemory( bits_MEMORY_ADVANCE_NODE ) )
+ {
+ if ( pTarget )
+ m_iszTarget = pTarget->m_target;
+ }
+
+ NodeStart( m_iszTarget );
+ TaskComplete();
+ //Msg( "BM: Found node %s\n", STRING( m_iszTarget ) );
+ }
+ break;
+
+ case TASK_NODE_DELAY:
+ m_nodeTime = gpGlobals->curtime + pTask->flTaskData;
+ TaskComplete();
+ //Msg( "BM: FAIL! Delay %.2f\n", pTask->flTaskData );
+ break;
+
+ case TASK_PROCESS_NODE:
+ //Msg( "BM: Reached node %s\n", STRING( m_iszTarget ) );
+ NodeReach();
+ TaskComplete();
+ break;
+
+ case TASK_PLAY_NODE_PRESEQUENCE:
+ case TASK_PLAY_NODE_SEQUENCE:
+ {
+ const char *pSequence = NULL;
+ int iSequence;
+
+ if ( pTask->iTask == TASK_PLAY_NODE_SEQUENCE )
+ pSequence = GetNodeSequence();
+ else
+ pSequence = GetNodePresequence();
+
+ //Msg( "BM: Playing node sequence %s\n", pSequence );
+
+ if ( pSequence ) //ugh
+ {
+ iSequence = LookupSequence( pSequence );
+
+ if ( iSequence != -1 )
+ {
+ SetIdealActivity( ACT_DO_NOT_DISTURB );
+ SetSequence( iSequence );
+ SetCycle( 0.0f );
+
+ ResetSequenceInfo();
+ //Msg( "BM: Sequence %s %f\n", GetNodeSequence(), gpGlobals->curtime );
+ return;
+ }
+ }
+ TaskComplete();
+ }
+ break;
+
+ case TASK_NODE_YAW:
+ GetMotor()->SetIdealYaw( GetNodeYaw() );
+ TaskComplete();
+ break;
+
+ case TASK_WAIT_NODE:
+ m_flWait = gpGlobals->curtime + GetNodeDelay();
+
+ /*if ( GetTarget() && GetTarget()->GetSpawnFlags() & SF_INFOBM_WAIT )
+ Msg( "BM: Wait at node %s forever\n", STRING( m_iszTarget) );
+ else
+ Msg( "BM: Wait at node %s for %.2f\n", STRING( m_iszTarget ), GetNodeDelay() );*/
+ break;
+
+
+ case TASK_MOVE_TO_NODE_RANGE:
+ {
+ CBaseEntity *pTarget = GetTarget();
+
+ if ( !pTarget )
+ TaskFail( FAIL_NO_TARGET );
+ else
+ {
+ if ( ( pTarget->GetAbsOrigin() - GetAbsOrigin() ).Length() < GetNodeRange() )
+ TaskComplete();
+ else
+ {
+ Activity act = ACT_WALK;
+ if ( pTarget->GetSpawnFlags() & SF_INFOBM_RUN )
+ act = ACT_RUN;
+
+ AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, act );
+
+ if ( !GetNavigator()->SetGoal( goal ) )
+ {
+ TaskFail( NO_TASK_FAILURE );
+ }
+ }
+ }
+ }
+ //Msg( "BM: Moving to node %s\n", STRING( m_iszTarget ) );
+
+ break;
+
+ case TASK_MELEE_ATTACK1:
+ {
+
+ // Play an attack sound here
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "BigMomma.Attack" );
+
+ BaseClass::StartTask( pTask );
+ }
+
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_BigMomma::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_CHECK_NODE_PROXIMITY:
+ {
+ float distance;
+
+ if ( GetTarget() == NULL )
+ TaskFail( FAIL_NO_TARGET );
+ else
+ {
+ if ( GetNavigator()->IsGoalActive() )
+ {
+ distance = ( GetTarget()->GetAbsOrigin() - GetAbsOrigin() ).Length2D();
+ // Set the appropriate activity based on an overlapping range
+ // overlap the range to prevent oscillation
+ if ( distance < GetNodeRange() )
+ {
+ //Msg( "BM: Reached node PROXIMITY!!\n" );
+ TaskComplete();
+ GetNavigator()->ClearGoal(); // Stop moving
+ }
+ }
+ else
+ TaskComplete();
+ }
+ }
+
+ break;
+
+ case TASK_WAIT_NODE:
+ if ( GetTarget() != NULL && (GetTarget()->GetSpawnFlags() & SF_INFOBM_WAIT) )
+ return;
+
+ if ( gpGlobals->curtime > m_flWaitFinished )
+ TaskComplete();
+ //Msg( "BM: The WAIT is over!\n" );
+ break;
+
+ case TASK_PLAY_NODE_PRESEQUENCE:
+ case TASK_PLAY_NODE_SEQUENCE:
+
+ if ( IsSequenceFinished() )
+ {
+ CBaseEntity *pTarget = NULL;
+
+ if ( GetTarget() )
+ pTarget = gEntList.FindEntityByName( NULL, STRING( GetTarget()->m_target ) );
+
+ if ( pTarget )
+ {
+ SetActivity( ACT_IDLE );
+ TaskComplete();
+ }
+ }
+
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//
+// Returns number of events handled, 0 if none.
+//=========================================================
+void CNPC_BigMomma::HandleAnimEvent( animevent_t *pEvent )
+{
+ CPASAttenuationFilter filter( this );
+
+ Vector vecFwd, vecRight, vecUp;
+ QAngle angles;
+ angles = GetAbsAngles();
+ AngleVectors( angles, &vecFwd, &vecRight, &vecUp );
+
+ switch( pEvent->event )
+ {
+ case BIG_AE_MELEE_ATTACKBR:
+ case BIG_AE_MELEE_ATTACKBL:
+ case BIG_AE_MELEE_ATTACK1:
+ {
+ Vector center = GetAbsOrigin() + vecFwd * 128;
+ Vector mins = center - Vector( 64, 64, 0 );
+ Vector maxs = center + Vector( 64, 64, 64 );
+
+ CBaseEntity *pList[8];
+ int count = UTIL_EntitiesInBox( pList, 8, mins, maxs, FL_NPC | FL_CLIENT );
+ CBaseEntity *pHurt = NULL;
+
+ for ( int i = 0; i < count && !pHurt; i++ )
+ {
+ if ( pList[i] != this )
+ {
+ if ( pList[i]->GetOwnerEntity() != this )
+ {
+ pHurt = pList[i];
+ }
+ }
+ }
+
+ if ( pHurt )
+ {
+ CTakeDamageInfo info( this, this, 15, DMG_CLUB | DMG_SLASH );
+ CalculateMeleeDamageForce( &info, (pHurt->GetAbsOrigin() - GetAbsOrigin()), pHurt->GetAbsOrigin() );
+ pHurt->TakeDamage( info );
+ QAngle newAngles = angles;
+ newAngles.x = 15;
+ if ( pHurt->IsPlayer() )
+ {
+ ((CBasePlayer *)pHurt)->SetPunchAngle( newAngles );
+ }
+ switch( pEvent->event )
+ {
+ case BIG_AE_MELEE_ATTACKBR:
+// pHurt->pev->velocity = pHurt->pev->velocity + (vecFwd * 150) + Vector(0,0,250) - (vecRight * 200);
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (vecFwd * 150) + Vector(0,0,250) - (vecRight * 200) );
+ break;
+
+ case BIG_AE_MELEE_ATTACKBL:
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (vecFwd * 150) + Vector(0,0,250) + (vecRight * 200) );
+ break;
+
+ case BIG_AE_MELEE_ATTACK1:
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (vecFwd * 220) + Vector(0,0,200) );
+ break;
+ }
+
+ pHurt->SetGroundEntity( NULL );
+ EmitSound( filter, entindex(), "BigMomma.AttackHit" );
+ }
+ }
+ break;
+
+ case BIG_AE_SCREAM:
+ EmitSound( filter, entindex(), "BigMomma.Alert" );
+ break;
+
+ case BIG_AE_PAIN_SOUND:
+ EmitSound( filter, entindex(), "BigMomma.Pain" );
+ break;
+
+ case BIG_AE_ATTACK_SOUND:
+ EmitSound( filter, entindex(), "BigMomma.Attack" );
+ break;
+
+ case BIG_AE_BIRTH_SOUND:
+ EmitSound( filter, entindex(), "BigMomma.Birth" );
+ break;
+
+ case BIG_AE_SACK:
+ if ( RandomInt(0,100) < 30 )
+ {
+ EmitSound( filter, entindex(), "BigMomma.Sack" );
+ }
+ break;
+
+ case BIG_AE_DEATHSOUND:
+ EmitSound( filter, entindex(), "BigMomma.Die" );
+ break;
+
+ case BIG_AE_STEP1: // Footstep left
+ case BIG_AE_STEP3: // Footstep back left
+ EmitSound( filter, entindex(), "BigMomma.FootstepLeft" );
+ break;
+
+ case BIG_AE_STEP4: // Footstep back right
+ case BIG_AE_STEP2: // Footstep right
+ EmitSound( filter, entindex(), "BigMomma.FootstepRight" );
+ break;
+
+ case BIG_AE_MORTAR_ATTACK1:
+ LaunchMortar();
+ break;
+
+ case BIG_AE_LAY_CRAB:
+ LayHeadcrab();
+ break;
+
+ case BIG_AE_JUMP_FORWARD:
+ SetGroundEntity( NULL );
+ SetAbsOrigin(GetAbsOrigin() + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground
+ SetAbsVelocity(vecFwd * 200 + vecUp * 500 );
+ break;
+
+ case BIG_AE_EARLY_TARGET:
+ {
+ CInfoBM *pTarget = (CInfoBM*) GetTarget();
+
+ if ( pTarget )
+ {
+ pTarget->m_OnAnimationEvent.FireOutput( this, this );
+ }
+
+ Remember( bits_MEMORY_FIRED_NODE );
+ }
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+
+void CNPC_BigMomma::LayHeadcrab( void )
+{
+ CBaseEntity *pChild = CBaseEntity::Create( BIG_CHILDCLASS, GetAbsOrigin(), GetAbsAngles(), this );
+
+ pChild->AddSpawnFlags( SF_NPC_FALL_TO_GROUND );
+
+ pChild->SetOwnerEntity( this );
+
+ // Is this the second crab in a pair?
+ if ( HasMemory( bits_MEMORY_CHILDPAIR ) )
+ {
+ m_crabTime = gpGlobals->curtime + RandomFloat( 5, 10 );
+ Forget( bits_MEMORY_CHILDPAIR );
+ }
+ else
+ {
+ m_crabTime = gpGlobals->curtime + RandomFloat( 0.5, 2.5 );
+ Remember( bits_MEMORY_CHILDPAIR );
+ }
+
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector(0,0,100), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ UTIL_DecalTrace( &tr, "Splash" );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "BigMomma.LayHeadcrab" );
+
+ m_crabCount++;
+}
+
+void CNPC_BigMomma::DeathNotice( CBaseEntity *pevChild )
+{
+ if ( m_crabCount > 0 ) // Some babies may cross a transition, but we reset the count then
+ {
+ m_crabCount--;
+ }
+ if ( IsAlive() )
+ {
+ // Make the "my baby's dead" noise!
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "BigMomma.ChildDie" );
+ }
+}
+
+
+void CNPC_BigMomma::LaunchMortar( void )
+{
+ m_mortarTime = gpGlobals->curtime + RandomFloat( 2, 15 );
+
+ Vector startPos = GetAbsOrigin();
+ startPos.z += 180;
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "BigMomma.LaunchMortar" );
+
+ CBMortar *pBomb = CBMortar::Shoot( this, startPos, m_vTossDir );
+ pBomb->SetGravity( 1.0 );
+ MortarSpray( startPos, Vector(0,0,10), gSpitSprite, 24 );
+}
+
+int CNPC_BigMomma::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ if (flDot >= 0.7)
+ {
+ if ( flDist > BIG_ATTACKDIST )
+ return COND_TOO_FAR_TO_ATTACK;
+ else
+ return COND_CAN_MELEE_ATTACK1;
+ }
+ else
+ {
+ return COND_NOT_FACING_ATTACK;
+ }
+
+ return COND_NONE;
+}
+
+
+// Lay a crab
+int CNPC_BigMomma::MeleeAttack2Conditions( float flDot, float flDist )
+{
+ return CanLayCrab();
+}
+
+
+Vector VecCheckSplatToss( CBaseEntity *pEnt, const Vector &vecSpot1, Vector vecSpot2, float maxHeight )
+{
+ trace_t tr;
+ Vector vecMidPoint;// halfway point between Spot1 and Spot2
+ Vector vecApex;// highest point
+ Vector vecScale;
+ Vector vecGrenadeVel;
+ Vector vecTemp;
+ float flGravity = GetCurrentGravity();
+
+ // calculate the midpoint and apex of the 'triangle'
+ vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5;
+ UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,maxHeight), MASK_SOLID_BRUSHONLY, pEnt, COLLISION_GROUP_NONE, &tr );
+ vecApex = tr.endpos;
+
+ UTIL_TraceLine(vecSpot1, vecApex, MASK_SOLID, pEnt, COLLISION_GROUP_NONE, &tr );
+ if (tr.fraction != 1.0)
+ {
+ // fail!
+ return vec3_origin;
+ }
+
+ // Don't worry about actually hitting the target, this won't hurt us!
+
+ // How high should the grenade travel (subtract 15 so the grenade doesn't hit the ceiling)?
+ float height = (vecApex.z - vecSpot1.z) - 15;
+
+ //HACK HACK
+ if ( height < 0 )
+ height *= -1;
+
+ // How fast does the grenade need to travel to reach that height given gravity?
+ float speed = sqrt( 2 * flGravity * height );
+
+ // How much time does it take to get there?
+ float time = speed / flGravity;
+ vecGrenadeVel = (vecSpot2 - vecSpot1);
+ vecGrenadeVel.z = 0;
+
+ // Travel half the distance to the target in that time (apex is at the midpoint)
+ vecGrenadeVel = vecGrenadeVel * ( 0.5 / time );
+ // Speed to offset gravity at the desired height
+ vecGrenadeVel.z = speed;
+
+ return vecGrenadeVel;
+}
+
+// Mortar launch
+int CNPC_BigMomma::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( flDist > BIG_MORTARDIST )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ if ( flDist <= BIG_MORTARDIST && m_mortarTime < gpGlobals->curtime )
+ {
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( pEnemy )
+ {
+ Vector startPos = GetAbsOrigin();
+ startPos.z += 180;
+
+ m_vTossDir = VecCheckSplatToss( this, startPos, pEnemy->BodyTarget( GetAbsOrigin() ), random->RandomFloat( 150, 500 ) );
+
+ if ( m_vTossDir != vec3_origin )
+ return COND_CAN_RANGE_ATTACK1;
+ }
+ }
+
+ return COND_NONE;
+}
+
+// ---------------------------------
+//
+// Mortar
+//
+// ---------------------------------
+void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count )
+{
+ CPVSFilter filter( position );
+
+ te->SpriteSpray( filter, 0.0, &position, &direction, spriteModel, 200, 80, count );
+}
+
+
+// UNDONE: right now this is pretty much a copy of the squid spit with minor changes to the way it does damage
+void CBMortar:: Spawn( void )
+{
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+ SetClassname( "bmortar" );
+
+ SetSolid( SOLID_BBOX );
+
+ pSprite = CSprite::SpriteCreate( "sprites/mommaspit.vmt", GetAbsOrigin(), true );
+
+ if ( pSprite )
+ {
+ pSprite->SetAttachment( this, 0 );
+ pSprite->m_flSpriteFramerate = 5;
+
+ pSprite->m_nRenderMode = kRenderTransAlpha;
+ pSprite->SetBrightness( 255 );
+
+ m_iFrame = 0;
+
+ pSprite->SetScale( 2.5f );
+ }
+
+ UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
+
+ m_maxFrame = (float)modelinfo->GetModelFrameCount( GetModel() ) - 1;
+ m_flDmgTime = gpGlobals->curtime + 0.4;
+}
+
+void CBMortar::Animate( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ Vector vVelocity = GetAbsVelocity();
+
+ VectorNormalize( vVelocity );
+
+ if ( gpGlobals->curtime > m_flDmgTime )
+ {
+ m_flDmgTime = gpGlobals->curtime + 0.2;
+ MortarSpray( GetAbsOrigin() + Vector( 0, 0, 15 ), -vVelocity, gSpitSprite, 3 );
+ }
+ if ( m_iFrame++ )
+ {
+ if ( m_iFrame > m_maxFrame )
+ {
+ m_iFrame = 0;
+ }
+ }
+}
+
+CBMortar *CBMortar::Shoot( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity )
+{
+ CBMortar *pSpit = CREATE_ENTITY( CBMortar, "bmortar" );
+ pSpit->Spawn();
+
+ UTIL_SetOrigin( pSpit, vecStart );
+ pSpit->SetAbsVelocity( vecVelocity );
+ pSpit->SetOwnerEntity( pOwner );
+ pSpit->SetThink ( &CBMortar::Animate );
+ pSpit->SetNextThink( gpGlobals->curtime + 0.1 );
+
+ return pSpit;
+}
+
+void CBMortar::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "NPC_BigMomma.SpitTouch1" );
+ PrecacheScriptSound( "NPC_BigMomma.SpitHit1" );
+ PrecacheScriptSound( "NPC_BigMomma.SpitHit2" );
+}
+
+void CBMortar::Touch( CBaseEntity *pOther )
+{
+ trace_t tr;
+ int iPitch;
+
+ // splat sound
+ iPitch = random->RandomFloat( 90, 110 );
+
+ EmitSound( "NPC_BigMomma.SpitTouch1" );
+
+ switch ( random->RandomInt( 0, 1 ) )
+ {
+ case 0:
+ EmitSound( "NPC_BigMomma.SpitHit1" );
+ break;
+ case 1:
+ EmitSound( "NPC_BigMomma.SpitHit2" );
+ break;
+ }
+
+ if ( pOther->IsBSPModel() )
+ {
+ // make a splat on the wall
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 10, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ UTIL_DecalTrace( &tr, "Splash" );
+ }
+ else
+ {
+ tr.endpos = GetAbsOrigin();
+
+ Vector vVelocity = GetAbsVelocity();
+ VectorNormalize( vVelocity );
+
+ tr.plane.normal = -1 * vVelocity;
+ }
+ // make some flecks
+ MortarSpray( tr.endpos + Vector( 0, 0, 15 ), tr.plane.normal, gSpitSprite, 24 );
+
+ CBaseEntity *pOwner = GetOwnerEntity();
+
+ RadiusDamage( CTakeDamageInfo( this, pOwner, sk_bigmomma_dmg_blast.GetFloat(), DMG_ACID ), GetAbsOrigin(), sk_bigmomma_radius_blast.GetFloat(), CLASS_NONE, NULL );
+
+ UTIL_Remove( pSprite );
+ UTIL_Remove( this );
+}
+
+
+AI_BEGIN_CUSTOM_NPC( monster_bigmomma, CNPC_BigMomma )
+
+ DECLARE_TASK( TASK_MOVE_TO_NODE_RANGE )
+ DECLARE_TASK( TASK_FIND_NODE )
+ DECLARE_TASK( TASK_PLAY_NODE_PRESEQUENCE )
+ DECLARE_TASK( TASK_PLAY_NODE_SEQUENCE )
+ DECLARE_TASK( TASK_PROCESS_NODE )
+ DECLARE_TASK( TASK_WAIT_NODE )
+ DECLARE_TASK( TASK_NODE_DELAY )
+ DECLARE_TASK( TASK_NODE_YAW )
+ DECLARE_TASK( TASK_CHECK_NODE_PROXIMITY )
+
+
+ //=========================================================
+ // > SCHED_BIG_NODE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_NODE_FAIL,
+
+ " Tasks"
+ " TASK_NODE_DELAY 3"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_BIG_NODE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_BIG_NODE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_NODE_FAIL"
+ " TASK_STOP_MOVING 0"
+ " TASK_FIND_NODE 0"
+ " TASK_PLAY_NODE_PRESEQUENCE 0"
+ " TASK_MOVE_TO_NODE_RANGE 0"
+ " TASK_CHECK_NODE_PROXIMITY 0"
+ " TASK_STOP_MOVING 0"
+ " TASK_NODE_YAW 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_WAIT_NODE 0"
+ " TASK_PLAY_NODE_SEQUENCE 0"
+ " TASK_PROCESS_NODE 0"
+
+ " "
+ " Interrupts"
+ )
+
+AI_END_CUSTOM_NPC()
+
+
diff --git a/game/server/hl1/hl1_npc_bloater.cpp b/game/server/hl1/hl1_npc_bloater.cpp
new file mode 100644
index 0000000..b5c57c9
--- /dev/null
+++ b/game/server/hl1/hl1_npc_bloater.cpp
@@ -0,0 +1,86 @@
+//========= 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 "hl1_ai_basenpc.h"
+
+class CNPC_Bloater : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Bloater, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ /*void SetYawSpeed( void );
+ int Classify ( void );
+ void HandleAnimEvent( MonsterEvent_t *pEvent );
+
+ void PainSound( const CTakeDamageInfo &info );
+ void AlertSound( void );
+ void IdleSound( void );
+ void AttackSnd( void );
+
+ // No range attacks
+ BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; }
+ BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; }
+ int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );*/
+};
+
+LINK_ENTITY_TO_CLASS( monster_bloater, CNPC_Bloater );
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Bloater::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/floater.mdl");
+// UTIL_SetSize( this, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_FLY );
+ m_spawnflags |= FL_FLY;
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ m_iHealth = 40;
+// pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin.
+ m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ NPCInit();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Bloater::Precache()
+{
+ PrecacheModel("models/floater.mdl");
+}
diff --git a/game/server/hl1/hl1_npc_bullsquid.cpp b/game/server/hl1/hl1_npc_bullsquid.cpp
new file mode 100644
index 0000000..373278d
--- /dev/null
+++ b/game/server/hl1/hl1_npc_bullsquid.cpp
@@ -0,0 +1,1167 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Cute hound like Alien.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "game.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_route.h"
+#include "ai_hint.h"
+#include "ai_navigator.h"
+#include "ai_senses.h"
+#include "npcevent.h"
+#include "animation.h"
+#include "hl1_npc_bullsquid.h"
+#include "gib.h"
+#include "soundent.h"
+#include "ndebugoverlay.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "hl1_grenade_spit.h"
+#include "util.h"
+#include "shake.h"
+#include "movevars_shared.h"
+#include "decals.h"
+#include "hl2_shareddefs.h"
+#include "ammodef.h"
+
+#define SQUID_SPRINT_DIST 256 // how close the squid has to get before starting to sprint and refusing to swerve
+
+ConVar sk_bullsquid_health ( "sk_bullsquid_health", "0" );
+ConVar sk_bullsquid_dmg_bite ( "sk_bullsquid_dmg_bite", "0" );
+ConVar sk_bullsquid_dmg_whip ( "sk_bullsquid_dmg_whip", "0" );
+extern ConVar sk_bullsquid_dmg_spit;
+
+//=========================================================
+// monster-specific schedule types
+//=========================================================
+enum
+{
+ SCHED_SQUID_HURTHOP = LAST_SHARED_SCHEDULE,
+ SCHED_SQUID_SEECRAB,
+ SCHED_SQUID_EAT,
+ SCHED_SQUID_SNIFF_AND_EAT,
+ SCHED_SQUID_WALLOW,
+ SCHED_SQUID_CHASE_ENEMY,
+};
+
+//=========================================================
+// monster-specific tasks
+//=========================================================
+enum
+{
+ TASK_SQUID_HOPTURN = LAST_SHARED_TASK,
+ TASK_SQUID_EAT,
+};
+
+//-----------------------------------------------------------------------------
+// Squid Conditions
+//-----------------------------------------------------------------------------
+enum
+{
+ COND_SQUID_SMELL_FOOD = LAST_SHARED_CONDITION + 1,
+};
+
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define BSQUID_AE_SPIT ( 1 )
+#define BSQUID_AE_BITE ( 2 )
+#define BSQUID_AE_BLINK ( 3 )
+#define BSQUID_AE_TAILWHIP ( 4 )
+#define BSQUID_AE_HOP ( 5 )
+#define BSQUID_AE_THROW ( 6 )
+
+LINK_ENTITY_TO_CLASS( monster_bullchicken, CNPC_Bullsquid );
+
+int ACT_SQUID_EXCITED;
+int ACT_SQUID_EAT;
+int ACT_SQUID_DETECT_SCENT;
+int ACT_SQUID_INSPECT_FLOOR;
+
+//=========================================================
+// Bullsquid's spit projectile
+//=========================================================
+class CSquidSpit : public CBaseEntity
+{
+ DECLARE_CLASS( CSquidSpit, CBaseEntity );
+public:
+ void Spawn( void );
+ void Precache( void );
+
+ static void Shoot( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity );
+ void Touch( CBaseEntity *pOther );
+ void Animate( void );
+
+ int m_nSquidSpitSprite;
+
+ DECLARE_DATADESC();
+
+ void SetSprite( CBaseEntity *pSprite )
+ {
+ m_hSprite = pSprite;
+ }
+
+ CBaseEntity *GetSprite( void )
+ {
+ return m_hSprite.Get();
+ }
+
+private:
+ EHANDLE m_hSprite;
+
+
+};
+
+LINK_ENTITY_TO_CLASS( squidspit, CSquidSpit );
+
+BEGIN_DATADESC( CSquidSpit )
+ DEFINE_FIELD( m_nSquidSpitSprite, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ),
+END_DATADESC()
+
+
+void CSquidSpit::Precache( void )
+{
+ m_nSquidSpitSprite = PrecacheModel("sprites/bigspit.vmt");// client side spittle.
+
+ PrecacheScriptSound( "NPC_BigMomma.SpitTouch1" );
+ PrecacheScriptSound( "NPC_BigMomma.SpitHit1" );
+ PrecacheScriptSound( "NPC_BigMomma.SpitHit2" );
+}
+
+void CSquidSpit:: Spawn( void )
+{
+ Precache();
+
+ SetMoveType ( MOVETYPE_FLY );
+ SetClassname( "squidspit" );
+
+ SetSolid( SOLID_BBOX );
+
+ m_nRenderMode = kRenderTransAlpha;
+ SetRenderColorA( 255 );
+ SetModel( "" );
+
+ SetSprite( CSprite::SpriteCreate( "sprites/bigspit.vmt", GetAbsOrigin(), true ) );
+
+ UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
+
+ SetCollisionGroup( HL2COLLISION_GROUP_SPIT );
+}
+
+void CSquidSpit::Shoot( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity )
+{
+ CSquidSpit *pSpit = CREATE_ENTITY( CSquidSpit, "squidspit" );
+ pSpit->Spawn();
+
+ UTIL_SetOrigin( pSpit, vecStart );
+ pSpit->SetAbsVelocity( vecVelocity );
+ pSpit->SetOwnerEntity( pOwner );
+
+ CSprite *pSprite = (CSprite*)pSpit->GetSprite();
+
+ if ( pSprite )
+ {
+ pSprite->SetAttachment( pSpit, 0 );
+ pSprite->SetOwnerEntity( pSpit );
+
+ pSprite->SetScale( 0.5 );
+ pSprite->SetTransparency( pSpit->m_nRenderMode, pSpit->m_clrRender->r, pSpit->m_clrRender->g, pSpit->m_clrRender->b, pSpit->m_clrRender->a, pSpit->m_nRenderFX );
+ }
+
+
+ CPVSFilter filter( vecStart );
+
+ VectorNormalize( vecVelocity );
+ te->SpriteSpray( filter, 0.0, &vecStart , &vecVelocity, pSpit->m_nSquidSpitSprite, 210, 25, 15 );
+}
+
+void CSquidSpit::Touch ( CBaseEntity *pOther )
+{
+ trace_t tr;
+ int iPitch;
+
+ if ( pOther->GetSolidFlags() & FSOLID_TRIGGER )
+ return;
+
+ if ( pOther->GetCollisionGroup() == HL2COLLISION_GROUP_SPIT)
+ {
+ return;
+ }
+
+ // splat sound
+ iPitch = random->RandomFloat( 90, 110 );
+
+ EmitSound( "NPC_BigMomma.SpitTouch1" );
+
+ switch ( random->RandomInt( 0, 1 ) )
+ {
+ case 0:
+ EmitSound( "NPC_BigMomma.SpitHit1" );
+ break;
+ case 1:
+ EmitSound( "NPC_BigMomma.SpitHit2" );
+ break;
+ }
+
+ if ( !pOther->m_takedamage )
+ {
+ // make a splat on the wall
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 10, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ UTIL_DecalTrace(&tr, "BeerSplash" );
+
+ // make some flecks
+ CPVSFilter filter( tr.endpos );
+
+ te->SpriteSpray( filter, 0.0, &tr.endpos, &tr.plane.normal, m_nSquidSpitSprite, 30, 8, 5 );
+
+ }
+ else
+ {
+ CTakeDamageInfo info( this, this, sk_bullsquid_dmg_spit.GetFloat(), DMG_BULLET );
+ CalculateBulletDamageForce( &info, GetAmmoDef()->Index("9mmRound"), GetAbsVelocity(), GetAbsOrigin() );
+ pOther->TakeDamage( info );
+ }
+
+ UTIL_Remove( m_hSprite );
+ UTIL_Remove( this );
+}
+
+
+BEGIN_DATADESC( CNPC_Bullsquid )
+ DEFINE_FIELD( m_fCanThreatDisplay, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flLastHurtTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextSpitTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flHungryTime, FIELD_TIME ),
+END_DATADESC()
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Bullsquid::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/bullsquid.mdl");
+ SetHullType(HULL_WIDE_SHORT);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_GREEN;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ m_iHealth = sk_bullsquid_health.GetFloat();
+ m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 );
+
+ m_fCanThreatDisplay = TRUE;
+ m_flNextSpitTime = gpGlobals->curtime;
+
+ NPCInit();
+
+ m_flDistTooFar = 784;
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Bullsquid::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheModel("models/bullsquid.mdl");
+
+ PrecacheModel("sprites/bigspit.vmt");// spit projectile.
+
+ PrecacheScriptSound( "Bullsquid.Idle" );
+ PrecacheScriptSound( "Bullsquid.Pain" );
+ PrecacheScriptSound( "Bullsquid.Alert" );
+ PrecacheScriptSound( "Bullsquid.Die" );
+ PrecacheScriptSound( "Bullsquid.Attack" );
+ PrecacheScriptSound( "Bullsquid.Bite" );
+ PrecacheScriptSound( "Bullsquid.Growl" );
+}
+
+
+int CNPC_Bullsquid::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ case SCHED_CHASE_ENEMY:
+ return SCHED_SQUID_CHASE_ENEMY;
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates this monster's place in the relationship table.
+// Output :
+//-----------------------------------------------------------------------------
+Class_T CNPC_Bullsquid::Classify( void )
+{
+ return CLASS_ALIEN_PREDATOR;
+}
+
+//=========================================================
+// IdleSound
+//=========================================================
+#define SQUID_ATTN_IDLE (float)1.5
+void CNPC_Bullsquid::IdleSound( void )
+{
+ CPASAttenuationFilter filter( this, SQUID_ATTN_IDLE );
+ EmitSound( filter, entindex(), "Bullsquid.Idle" );
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_Bullsquid::PainSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Bullsquid.Pain" );
+}
+
+//=========================================================
+// AlertSound
+//=========================================================
+void CNPC_Bullsquid::AlertSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Bullsquid.Alert" );
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_Bullsquid::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Bullsquid.Die" );
+}
+
+//=========================================================
+// AttackSound
+//=========================================================
+void CNPC_Bullsquid::AttackSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Bullsquid.Attack" );
+}
+
+//=========================================================
+// SetYawSpeed - allows each sequence to have a different
+// turn rate associated with it.
+//=========================================================
+float CNPC_Bullsquid::MaxYawSpeed( void )
+{
+ float flYS = 0;
+
+ switch ( GetActivity() )
+ {
+ case ACT_WALK: flYS = 90; break;
+ case ACT_RUN: flYS = 90; break;
+ case ACT_IDLE: flYS = 90; break;
+ case ACT_RANGE_ATTACK1: flYS = 90; break;
+ default:
+ flYS = 90;
+ break;
+ }
+
+ return flYS;
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Bullsquid::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case BSQUID_AE_SPIT:
+ {
+ if ( GetEnemy() )
+ {
+ Vector vecSpitOffset;
+ Vector vecSpitDir;
+ Vector vRight, vUp, vForward;
+
+ AngleVectors ( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ // !!!HACKHACK - the spot at which the spit originates (in front of the mouth) was measured in 3ds and hardcoded here.
+ // we should be able to read the position of bones at runtime for this info.
+ vecSpitOffset = ( vRight * 8 + vForward * 60 + vUp * 50 );
+ vecSpitOffset = ( GetAbsOrigin() + vecSpitOffset );
+ vecSpitDir = ( ( GetEnemy()->BodyTarget( GetAbsOrigin() ) ) - vecSpitOffset );
+
+ VectorNormalize( vecSpitDir );
+
+ vecSpitDir.x += random->RandomFloat( -0.05, 0.05 );
+ vecSpitDir.y += random->RandomFloat( -0.05, 0.05 );
+ vecSpitDir.z += random->RandomFloat( -0.05, 0 );
+
+ AttackSound();
+
+ CSquidSpit::Shoot( this, vecSpitOffset, vecSpitDir * 900 );
+ }
+ }
+ break;
+
+ case BSQUID_AE_BITE:
+ {
+ // SOUND HERE!
+ CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), sk_bullsquid_dmg_bite.GetFloat(), DMG_SLASH );
+ if ( pHurt )
+ {
+ Vector forward, up;
+ AngleVectors( GetAbsAngles(), &forward, NULL, &up );
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - (forward * 100) );
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (up * 100) );
+ pHurt->SetGroundEntity( NULL );
+ }
+ }
+ break;
+
+ case BSQUID_AE_TAILWHIP:
+ {
+ CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), sk_bullsquid_dmg_whip.GetFloat(), DMG_SLASH | DMG_ALWAYSGIB );
+ if ( pHurt )
+ {
+ Vector right, up;
+ AngleVectors( GetAbsAngles(), NULL, &right, &up );
+
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ pHurt->ViewPunch( QAngle( 20, 0, -20 ) );
+
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (right * 200) );
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (up * 100) );
+ }
+ }
+ break;
+
+ case BSQUID_AE_BLINK:
+ {
+ // close eye.
+ m_nSkin = 1;
+ }
+ break;
+
+ case BSQUID_AE_HOP:
+ {
+ float flGravity = GetCurrentGravity();
+
+ // throw the squid up into the air on this frame.
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ SetGroundEntity( NULL );
+ }
+
+ // jump into air for 0.8 (24/30) seconds
+ Vector vecVel = GetAbsVelocity();
+ vecVel.z += ( 0.625 * flGravity ) * 0.5;
+ SetAbsVelocity( vecVel );
+ }
+ break;
+
+ case BSQUID_AE_THROW:
+ {
+ // squid throws its prey IF the prey is a client.
+ CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), 0, 0 );
+
+
+ if ( pHurt )
+ {
+ // croonchy bite sound
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Bullsquid.Bite" );
+
+ // screeshake transforms the viewmodel as well as the viewangle. No problems with seeing the ends of the viewmodels.
+ UTIL_ScreenShake( pHurt->GetAbsOrigin(), 25.0, 1.5, 0.7, 2, SHAKE_START );
+
+ if ( pHurt->IsPlayer() )
+ {
+ Vector forward, up;
+ AngleVectors( GetAbsAngles(), &forward, NULL, &up );
+
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + forward * 300 + up * 300 );
+ }
+ }
+ }
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ }
+}
+
+int CNPC_Bullsquid::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( IsMoving() && flDist >= 512 )
+ {
+ // squid will far too far behind if he stops running to spit at this distance from the enemy.
+ return ( COND_NONE );
+ }
+
+ if ( flDist > 85 && flDist <= 784 && flDot >= 0.5 && gpGlobals->curtime >= m_flNextSpitTime )
+ {
+ if ( GetEnemy() != NULL )
+ {
+ if ( fabs( GetAbsOrigin().z - GetEnemy()->GetAbsOrigin().z ) > 256 )
+ {
+ // don't try to spit at someone up really high or down really low.
+ return( COND_NONE );
+ }
+ }
+
+ if ( IsMoving() )
+ {
+ // don't spit again for a long time, resume chasing enemy.
+ m_flNextSpitTime = gpGlobals->curtime + 5;
+ }
+ else
+ {
+ // not moving, so spit again pretty soon.
+ m_flNextSpitTime = gpGlobals->curtime + 0.5;
+ }
+
+ return( COND_CAN_RANGE_ATTACK1 );
+ }
+
+ return( COND_NONE );
+}
+
+//=========================================================
+// MeleeAttack2Conditions - bullsquid is a big guy, so has a longer
+// melee range than most monsters. This is the tailwhip attack
+//=========================================================
+int CNPC_Bullsquid::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ if ( GetEnemy()->m_iHealth <= sk_bullsquid_dmg_whip.GetFloat() && flDist <= 85 && flDot >= 0.7 )
+ {
+ return ( COND_CAN_MELEE_ATTACK1 );
+ }
+
+ return( COND_NONE );
+}
+
+//=========================================================
+// MeleeAttack2Conditions - bullsquid is a big guy, so has a longer
+// melee range than most monsters. This is the bite attack.
+// this attack will not be performed if the tailwhip attack
+// is valid.
+//=========================================================
+int CNPC_Bullsquid::MeleeAttack2Conditions( float flDot, float flDist )
+{
+ if ( flDist <= 85 && flDot >= 0.7 && !HasCondition( COND_CAN_MELEE_ATTACK1 ) ) // The player & bullsquid can be as much as their bboxes
+ return ( COND_CAN_MELEE_ATTACK2 );
+
+ return( COND_NONE );
+}
+
+bool CNPC_Bullsquid::FValidateHintType ( CAI_Hint *pHint )
+{
+ if ( pHint->HintType() == HINT_HL1_WORLD_HUMAN_BLOOD )
+ return true;
+
+ Msg ( "Couldn't validate hint type" );
+
+ return false;
+}
+
+void CNPC_Bullsquid::RemoveIgnoredConditions( void )
+{
+ if ( m_flHungryTime > gpGlobals->curtime )
+ ClearCondition( COND_SQUID_SMELL_FOOD );
+
+ if ( gpGlobals->curtime - m_flLastHurtTime <= 20 )
+ {
+ // haven't been hurt in 20 seconds, so let the squid care about stink.
+ ClearCondition( COND_SMELL );
+ }
+
+ if ( GetEnemy() != NULL )
+ {
+ // ( Unless after a tasty headcrab, yumm ^_^ )
+ if ( FClassnameIs( GetEnemy(), "monster_headcrab" ) )
+ ClearCondition( COND_SMELL );
+ }
+}
+
+Disposition_t CNPC_Bullsquid::IRelationType( CBaseEntity *pTarget )
+{
+ if ( gpGlobals->curtime - m_flLastHurtTime < 5 && FClassnameIs ( pTarget, "monster_headcrab" ) )
+ {
+ // if squid has been hurt in the last 5 seconds, and is getting relationship for a headcrab,
+ // tell squid to disregard crab.
+ return D_NU;
+ }
+
+ return BaseClass::IRelationType ( pTarget );
+}
+
+//=========================================================
+// TakeDamage - overridden for bullsquid so we can keep track
+// of how much time has passed since it was last injured
+//=========================================================
+int CNPC_Bullsquid::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+
+#if 0 //Fix later.
+
+ float flDist;
+ Vector vecApex, vOffset;
+
+ // if the squid is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy,
+ // it will swerve. (whew).
+ if ( GetEnemy() != NULL && IsMoving() && pevAttacker == GetEnemy() && gpGlobals->curtime - m_flLastHurtTime > 3 )
+ {
+ flDist = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).Length2D();
+
+ if ( flDist > SQUID_SPRINT_DIST )
+ {
+ AI_Waypoint_t* pRoute = GetNavigator()->GetPath()->Route();
+
+ if ( pRoute )
+ {
+ flDist = ( GetAbsOrigin() - pRoute[ pRoute->iNodeID ].vecLocation ).Length2D();// reusing flDist.
+
+ if ( GetNavigator()->GetPath()->BuildTriangulationRoute( GetAbsOrigin(), pRoute[ pRoute->iNodeID ].vecLocation, flDist * 0.5, GetEnemy(), &vecApex, &vOffset, NAV_GROUND ) )
+ {
+ GetNavigator()->PrependWaypoint( vecApex, bits_WP_TO_DETOUR | bits_WP_DONT_SIMPLIFY );
+ }
+ }
+ }
+ }
+#endif
+
+ if ( !FClassnameIs ( inputInfo.GetAttacker(), "monster_headcrab" ) )
+ {
+ // don't forget about headcrabs if it was a headcrab that hurt the squid.
+ m_flLastHurtTime = gpGlobals->curtime;
+ }
+
+ return BaseClass::OnTakeDamage_Alive ( inputInfo );
+}
+
+//=========================================================
+// GetSoundInterests - returns a bit mask indicating which types
+// of sounds this monster regards. In the base class implementation,
+// monsters care about all sounds, but no scents.
+//=========================================================
+int CNPC_Bullsquid::GetSoundInterests ( void )
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_CARCASS |
+ SOUND_MEAT |
+ SOUND_GARBAGE |
+ SOUND_PLAYER;
+}
+
+//=========================================================
+// OnListened - monsters dig through the active sound list for
+// any sounds that may interest them. (smells, too!)
+//=========================================================
+void CNPC_Bullsquid::OnListened( void )
+{
+ AISoundIter_t iter;
+
+ CSound *pCurrentSound;
+
+ static int conditionsToClear[] =
+ {
+ COND_SQUID_SMELL_FOOD,
+ };
+
+ ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
+
+ pCurrentSound = GetSenses()->GetFirstHeardSound( &iter );
+
+ while ( pCurrentSound )
+ {
+ // the npc cares about this sound, and it's close enough to hear.
+ int condition = COND_NONE;
+
+ if ( !pCurrentSound->FIsSound() )
+ {
+ // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent
+ if ( pCurrentSound->IsSoundType( SOUND_MEAT | SOUND_CARCASS ) )
+ {
+ // the detected scent is a food item
+ condition = COND_SQUID_SMELL_FOOD;
+ }
+ }
+
+ if ( condition != COND_NONE )
+ SetCondition( condition );
+
+ pCurrentSound = GetSenses()->GetNextHeardSound( &iter );
+ }
+
+ BaseClass::OnListened();
+}
+
+//========================================================
+// RunAI - overridden for bullsquid because there are things
+// that need to be checked every think.
+//========================================================
+void CNPC_Bullsquid::RunAI ( void )
+{
+ // first, do base class stuff
+ BaseClass::RunAI();
+
+ if ( m_nSkin != 0 )
+ {
+ // close eye if it was open.
+ m_nSkin = 0;
+ }
+
+ if ( random->RandomInt( 0,39 ) == 0 )
+ {
+ m_nSkin = 1;
+ }
+
+ if ( GetEnemy() != NULL && GetActivity() == ACT_RUN )
+ {
+ // chasing enemy. Sprint for last bit
+ if ( (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length2D() < SQUID_SPRINT_DIST )
+ {
+ m_flPlaybackRate = 1.25;
+ }
+ }
+
+}
+
+//=========================================================
+// GetSchedule
+//=========================================================
+int CNPC_Bullsquid::SelectSchedule( void )
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_ALERT:
+ {
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ return SCHED_SQUID_HURTHOP;
+ }
+
+ if ( HasCondition( COND_SQUID_SMELL_FOOD ) )
+ {
+ CSound *pSound;
+
+ pSound = GetBestScent();
+
+ if ( pSound && (!FInViewCone ( pSound->GetSoundOrigin() ) || !FVisible ( pSound->GetSoundOrigin() )) )
+ {
+ // scent is behind or occluded
+ return SCHED_SQUID_SNIFF_AND_EAT;
+ }
+
+ // food is right out in the open. Just go get it.
+ return SCHED_SQUID_EAT;
+ }
+
+ if ( HasCondition( COND_SMELL ) )
+ {
+ // there's something stinky.
+ CSound *pSound;
+
+ pSound = GetBestScent();
+ if ( pSound )
+ return SCHED_SQUID_WALLOW;
+ }
+
+ break;
+ }
+ case NPC_STATE_COMBAT:
+ {
+// dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // call base class, all code to handle dead enemies is centralized there.
+ return BaseClass::SelectSchedule();
+ }
+
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ {
+ if ( m_fCanThreatDisplay && IRelationType( GetEnemy() ) == D_HT && FClassnameIs( GetEnemy(), "monster_headcrab" ) )
+ {
+ // this means squid sees a headcrab!
+ m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime.
+ return SCHED_SQUID_SEECRAB;
+ }
+ else
+ {
+ return SCHED_WAKE_ANGRY;
+ }
+ }
+
+ if ( HasCondition( COND_SQUID_SMELL_FOOD ) )
+ {
+ CSound *pSound;
+
+ pSound = GetBestScent();
+
+ if ( pSound && (!FInViewCone ( pSound->GetSoundOrigin() ) || !FVisible ( pSound->GetSoundOrigin() )) )
+ {
+ // scent is behind or occluded
+ return SCHED_SQUID_SNIFF_AND_EAT;
+ }
+
+ // food is right out in the open. Just go get it.
+ return SCHED_SQUID_EAT;
+ }
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ return SCHED_RANGE_ATTACK1;
+ }
+
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return SCHED_MELEE_ATTACK1;
+ }
+
+ if ( HasCondition( COND_CAN_MELEE_ATTACK2 ) )
+ {
+ return SCHED_MELEE_ATTACK2;
+ }
+
+ return SCHED_CHASE_ENEMY;
+
+ break;
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//=========================================================
+// FInViewCone - returns true is the passed vector is in
+// the caller's forward view cone. The dot product is performed
+// in 2d, making the view cone infinitely tall.
+//=========================================================
+bool CNPC_Bullsquid::FInViewCone ( Vector pOrigin )
+{
+ Vector los = ( pOrigin - GetAbsOrigin() );
+
+ // do this in 2D
+ los.z = 0;
+ VectorNormalize( los );
+
+ Vector facingDir = EyeDirection2D( );
+
+ float flDot = DotProduct( los, facingDir );
+
+ if ( flDot > m_flFieldOfView )
+ return true;
+
+ return false;
+}
+
+//=========================================================
+// FVisible - returns true if a line can be traced from
+// the caller's eyes to the target vector
+//=========================================================
+bool CNPC_Bullsquid::FVisible ( Vector vecOrigin )
+{
+ trace_t tr;
+ Vector vecLookerOrigin;
+
+ vecLookerOrigin = EyePosition();//look through the caller's 'eyes'
+ UTIL_TraceLine(vecLookerOrigin, vecOrigin, MASK_BLOCKLOS, this/*pentIgnore*/, COLLISION_GROUP_NONE, &tr);
+
+ if ( tr.fraction != 1.0 )
+ return false; // Line of sight is not established
+ else
+ return true;// line of sight is valid.
+}
+
+//=========================================================
+// Start task - selects the correct activity and performs
+// any necessary calculations to start the next task on the
+// schedule. OVERRIDDEN for bullsquid because it needs to
+// know explicitly when the last attempt to chase the enemy
+// failed, since that impacts its attack choices.
+//=========================================================
+void CNPC_Bullsquid::StartTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_MELEE_ATTACK2:
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Bullsquid.Growl" );
+ BaseClass::StartTask ( pTask );
+ break;
+ }
+ case TASK_SQUID_HOPTURN:
+ {
+ SetActivity ( ACT_HOP );
+
+ if ( GetEnemy() )
+ {
+ Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ VectorNormalize( vecFacing );
+
+ GetMotor()->SetIdealYaw( vecFacing );
+ }
+
+ break;
+ }
+ case TASK_SQUID_EAT:
+ {
+ m_flHungryTime = gpGlobals->curtime + pTask->flTaskData;
+ break;
+ }
+
+ default:
+ {
+ BaseClass::StartTask ( pTask );
+ break;
+ }
+ }
+}
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_Bullsquid::RunTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_SQUID_HOPTURN:
+ {
+ if ( GetEnemy() )
+ {
+ Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ VectorNormalize( vecFacing );
+ GetMotor()->SetIdealYaw( vecFacing );
+ }
+
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+ default:
+ {
+ BaseClass::RunTask( pTask );
+ break;
+ }
+ }
+}
+
+//=========================================================
+// GetIdealState - Overridden for Bullsquid to deal with
+// the feature that makes it lose interest in headcrabs for
+// a while if something injures it.
+//=========================================================
+NPC_STATE CNPC_Bullsquid::SelectIdealState ( void )
+{
+ // If no schedule conditions, the new ideal state is probably the reason we're in here.
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ /*
+ COMBAT goes to ALERT upon death of enemy
+ */
+ {
+ if ( GetEnemy() != NULL && ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition ( COND_HEAVY_DAMAGE ) ) && FClassnameIs( GetEnemy(), "monster_headcrab" ) )
+ {
+ // if the squid has a headcrab enemy and something hurts it, it's going to forget about the crab for a while.
+ SetEnemy( NULL );
+ return NPC_STATE_ALERT;
+ }
+ break;
+ }
+ }
+
+ return BaseClass::SelectIdealState();
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( monster_bullchicken, CNPC_Bullsquid )
+
+ DECLARE_TASK ( TASK_SQUID_HOPTURN )
+ DECLARE_TASK ( TASK_SQUID_EAT )
+
+ DECLARE_CONDITION( COND_SQUID_SMELL_FOOD )
+
+ DECLARE_ACTIVITY( ACT_SQUID_EXCITED )
+ DECLARE_ACTIVITY( ACT_SQUID_EAT )
+ DECLARE_ACTIVITY( ACT_SQUID_DETECT_SCENT )
+ DECLARE_ACTIVITY( ACT_SQUID_INSPECT_FLOOR )
+
+ //=========================================================
+ // > SCHED_SQUID_HURTHOP
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SQUID_HURTHOP,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SOUND_WAKE 0"
+ " TASK_SQUID_HOPTURN 0"
+ " TASK_FACE_ENEMY 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_SQUID_SEECRAB
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SQUID_SEECRAB,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SOUND_WAKE 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EXCITED"
+ " TASK_FACE_ENEMY 0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_SQUID_EAT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SQUID_EAT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SQUID_EAT 10"
+ " TASK_STORE_LASTPOSITION 0"
+ " TASK_GET_PATH_TO_BESTSCENT 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
+ " TASK_SQUID_EAT 50"
+ " TASK_GET_PATH_TO_LASTPOSITION 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_CLEAR_LASTPOSITION 0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_SMELL"
+ )
+
+ //=========================================================
+ // > SCHED_SQUID_SNIFF_AND_EAT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SQUID_SNIFF_AND_EAT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SQUID_EAT 10"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_DETECT_SCENT"
+ " TASK_STORE_LASTPOSITION 0"
+ " TASK_GET_PATH_TO_BESTSCENT 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT"
+ " TASK_SQUID_EAT 50"
+ " TASK_GET_PATH_TO_LASTPOSITION 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_CLEAR_LASTPOSITION 0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_SMELL"
+ )
+
+ //=========================================================
+ // > SCHED_SQUID_WALLOW
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SQUID_WALLOW,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SQUID_EAT 10"
+ " TASK_STORE_LASTPOSITION 0"
+ " TASK_GET_PATH_TO_BESTSCENT 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_INSPECT_FLOOR"
+ " TASK_SQUID_EAT 50"
+ " TASK_GET_PATH_TO_LASTPOSITION 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_CLEAR_LASTPOSITION 0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_SQUID_CHASE_ENEMY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SQUID_CHASE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1"
+ " TASK_GET_PATH_TO_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_SMELL"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_TASK_FAILED"
+ )
+
+AI_END_CUSTOM_NPC()
diff --git a/game/server/hl1/hl1_npc_bullsquid.h b/game/server/hl1/hl1_npc_bullsquid.h
new file mode 100644
index 0000000..1f7a52d
--- /dev/null
+++ b/game/server/hl1/hl1_npc_bullsquid.h
@@ -0,0 +1,68 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_BULLSQUID_H
+#define NPC_BULLSQUID_H
+
+#include "hl1_ai_basenpc.h"
+
+
+class CNPC_Bullsquid : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Bullsquid, CHL1BaseNPC );
+
+public:
+ void Spawn( void );
+ void Precache( void );
+ Class_T Classify( void );
+
+ void IdleSound( void );
+ void PainSound( const CTakeDamageInfo &info );
+ void AlertSound( void );
+ void DeathSound( const CTakeDamageInfo &info );
+ void AttackSound( void );
+
+ float MaxYawSpeed( void );
+
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ int RangeAttack1Conditions( float flDot, float flDist );
+ int MeleeAttack1Conditions( float flDot, float flDist );
+ int MeleeAttack2Conditions( float flDot, float flDist );
+
+ bool FValidateHintType ( CAI_Hint *pHint );
+ void RemoveIgnoredConditions( void );
+ Disposition_t IRelationType( CBaseEntity *pTarget );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+
+ int GetSoundInterests ( void );
+ void RunAI ( void );
+ virtual void OnListened ( void );
+
+ int SelectSchedule( void );
+ int TranslateSchedule( int scheduleType );
+
+ bool FInViewCone ( Vector pOrigin );
+ bool FVisible ( Vector vecOrigin );
+
+ void StartTask ( const Task_t *pTask );
+ void RunTask ( const Task_t *pTask );
+
+ NPC_STATE SelectIdealState ( void );
+
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC()
+
+private:
+
+ bool m_fCanThreatDisplay;// this is so the squid only does the "I see a headcrab!" dance one time.
+ float m_flLastHurtTime;// we keep track of this, because if something hurts a squid, it will forget about its love of headcrabs for a while.
+ float m_flNextSpitTime;// last time the bullsquid used the spit attack.
+ float m_flHungryTime;// set this is a future time to stop the monster from eating for a while.
+
+};
+#endif // NPC_BULLSQUID_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_controller.cpp b/game/server/hl1/hl1_npc_controller.cpp
new file mode 100644
index 0000000..bc27aa0
--- /dev/null
+++ b/game/server/hl1/hl1_npc_controller.cpp
@@ -0,0 +1,1313 @@
+//========= 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 "hl1_npc_controller.h"
+#include "ai_basenpc_flyer.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 "ai_moveprobe.h"
+
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define CONTROLLER_AE_HEAD_OPEN 1
+#define CONTROLLER_AE_BALL_SHOOT 2
+#define CONTROLLER_AE_SMALL_SHOOT 3
+#define CONTROLLER_AE_POWERUP_FULL 4
+#define CONTROLLER_AE_POWERUP_HALF 5
+
+#define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs
+
+#define DIST_TO_CHECK 200
+
+ConVar sk_controller_health ( "sk_controller_health", "60" );
+ConVar sk_controller_dmgzap ( "sk_controller_dmgzap", "15" );
+ConVar sk_controller_speedball ( "sk_controller_speedball", "650" );
+ConVar sk_controller_dmgball ( "sk_controller_dmgball", "3" );
+
+int ACT_CONTROLLER_UP;
+int ACT_CONTROLLER_DOWN;
+int ACT_CONTROLLER_LEFT;
+int ACT_CONTROLLER_RIGHT;
+int ACT_CONTROLLER_FORWARD;
+int ACT_CONTROLLER_BACKWARD;
+
+class CSprite;
+class CNPC_Controller;
+
+enum
+{
+ TASK_CONTROLLER_CHASE_ENEMY = LAST_SHARED_TASK,
+ TASK_CONTROLLER_STRAFE,
+ TASK_CONTROLLER_TAKECOVER,
+ TASK_CONTROLLER_FAIL,
+};
+
+enum
+{
+ SCHED_CONTROLLER_CHASE_ENEMY = LAST_SHARED_SCHEDULE,
+ SCHED_CONTROLLER_STRAFE,
+ SCHED_CONTROLLER_TAKECOVER,
+ SCHED_CONTROLLER_FAIL,
+};
+
+class CControllerNavigator : public CAI_ComponentWithOuter<CNPC_Controller, CAI_Navigator>
+{
+ typedef CAI_ComponentWithOuter<CNPC_Controller, CAI_Navigator> BaseClass;
+public:
+ CControllerNavigator( CNPC_Controller *pOuter )
+ : BaseClass( pOuter )
+ {
+ }
+
+ bool ActivityIsLocomotive( Activity activity ) { return true; }
+};
+
+class CNPC_Controller : public CAI_BaseFlyingBot
+{
+public:
+
+ DECLARE_CLASS( CNPC_Controller, CAI_BaseFlyingBot );
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ float MaxYawSpeed( void ) { return 120.0f; }
+ Class_T Classify ( void ) { return CLASS_ALIEN_MILITARY; }
+
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ void RunAI( void );
+
+ int RangeAttack1Conditions ( float flDot, float flDist ); // balls
+ int RangeAttack2Conditions ( float flDot, float flDist ); // head
+ int MeleeAttack1Conditions ( float flDot, float flDist ) { return COND_NONE; }
+ int MeleeAttack2Conditions ( float flDot, float flDist ) { return COND_NONE; }
+
+ int TranslateSchedule( int scheduleType );
+ void StartTask ( const Task_t *pTask );
+ void RunTask ( const Task_t *pTask );
+
+ void Stop( void );
+ bool OverridePathMove( float flInterval );
+ bool OverrideMove( float flInterval );
+
+ void MoveToTarget( float flInterval, const Vector &vecMoveTarget );
+
+ Activity NPC_TranslateActivity( Activity eNewActivity );
+ void SetActivity ( Activity NewActivity );
+ bool ShouldAdvanceRoute( float flWaypointDist );
+ int LookupFloat( );
+
+ friend class CControllerNavigator;
+ CAI_Navigator *CreateNavigator()
+ {
+ return new CControllerNavigator( this );
+ }
+
+ bool ShouldGib( const CTakeDamageInfo &info );
+ bool HasAlienGibs( void ) { return true; }
+ bool HasHumanGibs( void ) { return false; }
+
+ float m_flShootTime;
+ float m_flShootEnd;
+
+ void PainSound( const CTakeDamageInfo &info );
+ void AlertSound( void );
+ void IdleSound( void );
+ void AttackSound( void );
+ void DeathSound( const CTakeDamageInfo &info );
+
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ CSprite *m_pBall[2]; // hand balls
+ int m_iBall[2]; // how bright it should be
+ float m_iBallTime[2]; // when it should be that color
+ int m_iBallCurrent[2]; // current brightness
+
+ Vector m_vecEstVelocity;
+
+ Vector m_velocity;
+ bool m_fInCombat;
+
+ void SetSequence( int nSequence );
+
+ int IRelationPriority( CBaseEntity *pTarget );
+};
+
+class CNPC_ControllerHeadBall : public CAI_BaseNPC
+{
+public:
+ DECLARE_CLASS( CNPC_ControllerHeadBall, CAI_BaseNPC );
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ void EXPORT HuntThink( void );
+ void EXPORT KillThink( void );
+ void EXPORT BounceTouch( CBaseEntity *pOther );
+ void MovetoTarget( Vector vecTarget );
+
+ float m_flSpawnTime;
+ Vector m_vecIdeal;
+ EHANDLE m_hOwner;
+
+ CSprite *m_pSprite;
+};
+
+class CNPC_ControllerZapBall : public CAI_BaseNPC
+{
+public:
+ DECLARE_CLASS( CNPC_ControllerHeadBall, CAI_BaseNPC );
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ void EXPORT AnimateThink( void );
+ void EXPORT ExplodeTouch( CBaseEntity *pOther );
+
+ void Kill( void );
+
+ EHANDLE m_hOwner;
+ float m_flSpawnTime;
+
+ CSprite *m_pSprite;
+};
+
+LINK_ENTITY_TO_CLASS( monster_alien_controller, CNPC_Controller );
+
+BEGIN_DATADESC( CNPC_Controller )
+
+ DEFINE_ARRAY( m_pBall, FIELD_CLASSPTR, 2 ),
+ DEFINE_ARRAY( m_iBall, FIELD_INTEGER, 2 ),
+ DEFINE_ARRAY( m_iBallTime, FIELD_TIME, 2 ),
+ DEFINE_ARRAY( m_iBallCurrent, FIELD_INTEGER, 2 ),
+ DEFINE_FIELD( m_vecEstVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_velocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_fInCombat, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_flShootTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flShootEnd, FIELD_TIME ),
+
+END_DATADESC()
+
+
+void CNPC_Controller::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/controller.mdl" );
+ UTIL_SetSize( this, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ));
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ SetMoveType( MOVETYPE_STEP );
+ SetGravity(0.001);
+
+
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ m_iHealth = sk_controller_health.GetFloat();
+
+ m_flFieldOfView = VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ CapabilitiesClear();
+
+ AddFlag( FL_FLY );
+ SetNavType( NAV_FLY );
+
+ CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_MOVE_SHOOT);
+
+ NPCInit();
+
+
+ SetDefaultEyeOffset();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Controller::Precache()
+{
+ PrecacheModel("models/controller.mdl");
+
+ PrecacheModel( "sprites/xspark4.vmt");
+
+ UTIL_PrecacheOther( "controller_energy_ball" );
+ UTIL_PrecacheOther( "controller_head_ball" );
+
+ PrecacheScriptSound( "Controller.Pain" );
+ PrecacheScriptSound( "Controller.Alert" );
+ PrecacheScriptSound( "Controller.Die" );
+ PrecacheScriptSound( "Controller.Idle" );
+ PrecacheScriptSound( "Controller.Attack" );
+
+}
+
+//=========================================================
+// TakeDamage -
+//=========================================================
+int CNPC_Controller::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ PainSound( info );
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+bool CNPC_Controller::ShouldGib( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamageType() & DMG_NEVERGIB )
+ return false;
+
+ if ( ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) && m_iHealth < GIB_HEALTH_VALUE ) || ( info.GetDamageType() & DMG_ALWAYSGIB ) )
+ return true;
+
+ return false;
+
+}
+
+int CNPC_Controller::IRelationPriority( CBaseEntity *pTarget )
+{
+ if ( pTarget->Classify() == CLASS_PLAYER )
+ {
+ return BaseClass::IRelationPriority ( pTarget ) + 1;
+ }
+
+ return BaseClass::IRelationPriority( pTarget );
+}
+
+void CNPC_Controller::Event_Killed( const CTakeDamageInfo &info )
+{
+ if( ShouldGib(info) )
+ {
+ //remove the balls
+ if (m_pBall[0])
+ {
+ UTIL_Remove( m_pBall[0] );
+ m_pBall[0] = NULL;
+ }
+ if (m_pBall[1])
+ {
+ UTIL_Remove( m_pBall[1] );
+ m_pBall[1] = NULL;
+ }
+ }
+ else
+ {
+ // fade balls
+ if (m_pBall[0])
+ {
+ m_pBall[0]->FadeAndDie( 2 );
+ m_pBall[0] = NULL;
+ }
+ if (m_pBall[1])
+ {
+ m_pBall[1]->FadeAndDie( 2 );
+ m_pBall[1] = NULL;
+ }
+ }
+
+ BaseClass::Event_Killed( info );
+}
+
+void CNPC_Controller::PainSound( const CTakeDamageInfo &info )
+{
+ if (random->RandomInt(0,5) < 2)
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Pain" );
+ }
+}
+
+void CNPC_Controller::AlertSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Alert" );
+}
+
+void CNPC_Controller::IdleSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Idle" );
+}
+
+void CNPC_Controller::AttackSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Attack" );
+}
+
+void CNPC_Controller::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Die" );
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Controller::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case CONTROLLER_AE_HEAD_OPEN:
+ {
+ Vector vecStart;
+ QAngle angleGun;
+
+ GetAttachment( 0, vecStart, angleGun );
+
+ // BUGBUG - attach to attachment point!
+
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0, 1 /*radius*/, 0.2, -32 );
+
+ m_iBall[0] = 192;
+ m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ m_iBall[1] = 255;
+ m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+
+ }
+ break;
+
+ case CONTROLLER_AE_BALL_SHOOT:
+ {
+ Vector vecStart;
+ QAngle angleGun;
+
+ GetAttachment( 1, vecStart, angleGun );
+
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0, 1 /*radius*/, 0.1, 32 );
+
+ CAI_BaseNPC *pBall = (CAI_BaseNPC*)Create( "controller_head_ball", vecStart, angleGun );
+
+ pBall->SetAbsVelocity( Vector(0,0,32) );
+ pBall->SetEnemy( GetEnemy() );
+
+// DevMsg( 1, "controller shooting head ball\n" );
+
+ m_iBall[0] = 0;
+ m_iBall[1] = 0;
+ }
+ break;
+
+ case CONTROLLER_AE_SMALL_SHOOT:
+ {
+ AttackSound( );
+ m_flShootTime = gpGlobals->curtime;
+ m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0;
+ }
+ break;
+ case CONTROLLER_AE_POWERUP_FULL:
+ {
+ m_iBall[0] = 255;
+ m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ m_iBall[1] = 255;
+ m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ }
+ break;
+ case CONTROLLER_AE_POWERUP_HALF:
+ {
+ m_iBall[0] = 192;
+ m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ m_iBall[1] = 192;
+ m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ }
+ break;
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+
+//=========================================================
+// AI Schedules Specific to this monster
+//=========================================================
+
+AI_BEGIN_CUSTOM_NPC( monster_alien_controller, CNPC_Controller )
+
+ //declare our tasks
+ DECLARE_TASK( TASK_CONTROLLER_CHASE_ENEMY )
+ DECLARE_TASK( TASK_CONTROLLER_STRAFE )
+ DECLARE_TASK( TASK_CONTROLLER_TAKECOVER )
+ DECLARE_TASK( TASK_CONTROLLER_FAIL )
+
+ DECLARE_ACTIVITY( ACT_CONTROLLER_UP )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_DOWN )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_LEFT )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_RIGHT )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_FORWARD )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_BACKWARD )
+
+ //=========================================================
+ // > SCHED_CONTROLLER_CHASE_ENEMY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CONTROLLER_CHASE_ENEMY,
+
+ " Tasks"
+ " TASK_GET_PATH_TO_ENEMY 128"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_TASK_FAILED"
+
+
+ )
+
+ //=========================================================
+ // > SCHED_CONTROLLER_STRAFE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CONTROLLER_STRAFE,
+
+ " Tasks"
+ " TASK_WAIT 0.2"
+ " TASK_GET_PATH_TO_ENEMY 128"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_CONTROLLER_TAKECOVER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CONTROLLER_TAKECOVER,
+
+ " Tasks"
+ " TASK_WAIT 0.2"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_CONTROLLER_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CONTROLLER_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2"
+ " TASK_WAIT_PVS 0"
+ )
+
+AI_END_CUSTOM_NPC()
+
+//=========================================================
+// StartTask
+//=========================================================
+void CNPC_Controller::StartTask( const Task_t *pTask )
+{
+ BaseClass::StartTask( pTask );
+}
+
+Vector Intersect( Vector vecSrc, Vector vecDst, Vector vecMove, float flSpeed )
+{
+ Vector vecTo = vecDst - vecSrc;
+
+ float a = DotProduct( vecMove, vecMove ) - flSpeed * flSpeed;
+ float b = 0 * DotProduct(vecTo, vecMove); // why does this work?
+ float c = DotProduct( vecTo, vecTo );
+
+ float t;
+ if (a == 0)
+ {
+ t = c / (flSpeed * flSpeed);
+ }
+ else
+ {
+ t = b * b - 4 * a * c;
+ t = sqrt( t ) / (2.0 * a);
+ float t1 = -b +t;
+ float t2 = -b -t;
+
+ if (t1 < 0 || t2 < t1)
+ t = t2;
+ else
+ t = t1;
+ }
+
+ if (t < 0.1)
+ t = 0.1;
+ if (t > 10.0)
+ t = 10.0;
+
+ Vector vecHit = vecTo + vecMove * t;
+ VectorNormalize( vecHit );
+ return vecHit * flSpeed;
+}
+
+
+int CNPC_Controller::LookupFloat( )
+{
+ if (m_velocity.Length( ) < 32.0)
+ {
+ return ACT_CONTROLLER_UP;
+ }
+
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( GetAbsAngles(), &vecForward, &vecRight, &vecUp );
+
+ float x = DotProduct( vecForward, m_velocity );
+ float y = DotProduct( vecRight, m_velocity );
+ float z = DotProduct( vecUp, m_velocity );
+
+ if (fabs(x) > fabs(y) && fabs(x) > fabs(z))
+ {
+ if (x > 0)
+ return ACT_CONTROLLER_FORWARD;
+ else
+ return ACT_CONTROLLER_BACKWARD;
+ }
+ else if (fabs(y) > fabs(z))
+ {
+ if (y > 0)
+ return ACT_CONTROLLER_RIGHT;
+ else
+ return ACT_CONTROLLER_LEFT;
+ }
+ else
+ {
+ if (z > 0)
+ return ACT_CONTROLLER_UP;
+ else
+ return ACT_CONTROLLER_DOWN;
+ }
+}
+
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_Controller::RunTask ( const Task_t *pTask )
+{
+ if (m_flShootEnd > gpGlobals->curtime)
+ {
+ Vector vecHand;
+ QAngle vecAngle;
+
+ GetAttachment( 2, vecHand, vecAngle );
+
+ while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->curtime)
+ {
+ Vector vecSrc = vecHand + GetAbsVelocity() * (m_flShootTime - gpGlobals->curtime);
+ Vector vecDir;
+
+ if (GetEnemy() != NULL)
+ {
+ if (HasCondition( COND_SEE_ENEMY ))
+ {
+ m_vecEstVelocity = m_vecEstVelocity * 0.5 + GetEnemy()->GetAbsVelocity() * 0.5;
+ }
+ else
+ {
+ m_vecEstVelocity = m_vecEstVelocity * 0.8;
+ }
+ vecDir = Intersect( vecSrc, GetEnemy()->BodyTarget( GetAbsOrigin() ), m_vecEstVelocity, sk_controller_speedball.GetFloat() );
+
+ float delta = 0.03490; // +-2 degree
+ vecDir = vecDir + Vector( random->RandomFloat( -delta, delta ), random->RandomFloat( -delta, delta ), random->RandomFloat( -delta, delta ) ) * sk_controller_speedball.GetFloat();
+
+ vecSrc = vecSrc + vecDir * (gpGlobals->curtime - m_flShootTime);
+ CAI_BaseNPC *pBall = (CAI_BaseNPC*)Create( "controller_energy_ball", vecSrc, GetAbsAngles(), this );
+ pBall->SetAbsVelocity( vecDir );
+
+// DevMsg( 2, "controller shooting energy ball\n" );
+ }
+
+ m_flShootTime += 0.2;
+ }
+
+ if (m_flShootTime > m_flShootEnd)
+ {
+ m_iBall[0] = 64;
+ m_iBallTime[0] = m_flShootEnd;
+ m_iBall[1] = 64;
+ m_iBallTime[1] = m_flShootEnd;
+ m_fInCombat = FALSE;
+ }
+ }
+
+ switch ( pTask->iTask )
+ {
+ case TASK_WAIT_FOR_MOVEMENT:
+ case TASK_WAIT:
+ case TASK_WAIT_FACE_ENEMY:
+ case TASK_WAIT_PVS:
+ {
+ if( GetEnemy() )
+ {
+ float idealYaw = UTIL_VecToYaw( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ GetMotor()->SetIdealYawAndUpdate( idealYaw );
+ }
+
+ if ( IsSequenceFinished() || GetActivity() == ACT_IDLE)
+ {
+ m_fInCombat = false;
+ }
+
+ BaseClass::RunTask ( pTask );
+
+ if (!m_fInCombat)
+ {
+ if( HasCondition( COND_CAN_RANGE_ATTACK1 ))
+ {
+ SetActivity( ACT_RANGE_ATTACK1 );
+ SetCycle( 0 );
+ ResetSequenceInfo( );
+ m_fInCombat = true;
+ }
+ else if( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ SetActivity( ACT_RANGE_ATTACK2 );
+ SetCycle( 0 );
+ ResetSequenceInfo( );
+ m_fInCombat = true;
+ }
+ else
+ {
+ int iFloatActivity = LookupFloat();
+ if( IsSequenceFinished() || iFloatActivity != GetActivity() )
+ {
+ SetActivity( (Activity)iFloatActivity );
+ }
+ }
+ }
+ }
+ break;
+ default:
+ BaseClass::RunTask ( pTask );
+ break;
+ }
+}
+
+void CNPC_Controller::SetSequence( int nSequence )
+{
+ BaseClass::SetSequence( nSequence );
+}
+
+
+//=========================================================
+//=========================================================
+int CNPC_Controller::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ case SCHED_CHASE_ENEMY:
+ return SCHED_CONTROLLER_CHASE_ENEMY;
+ case SCHED_RANGE_ATTACK1:
+ return SCHED_CONTROLLER_STRAFE;
+ case SCHED_RANGE_ATTACK2:
+ case SCHED_MELEE_ATTACK1:
+ case SCHED_MELEE_ATTACK2:
+ case SCHED_TAKE_COVER_FROM_ENEMY:
+ return SCHED_CONTROLLER_TAKECOVER;
+ case SCHED_FAIL:
+ return SCHED_CONTROLLER_FAIL;
+
+ default:
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+
+//=========================================================
+// CheckRangeAttack1 - shoot a bigass energy ball out of their head
+//=========================================================
+int CNPC_Controller::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ if( flDist > 2048 )
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if( flDist <= 256 )
+ {
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+
+// if( flDot <= 0.5 )
+// {
+// return COND_NOT_FACING_ATTACK;
+// }
+
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+//=========================================================
+// CheckRangeAttack1 - head
+//=========================================================
+int CNPC_Controller::RangeAttack2Conditions ( float flDot, float flDist )
+{
+ if( flDist > 2048 )
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if( flDist <= 64 )
+ {
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+
+// if( flDot <= 0.5 )
+// {
+// return COND_NOT_FACING_ATTACK;
+// }
+
+ return COND_CAN_RANGE_ATTACK2;
+}
+
+//=========================================================
+//=========================================================
+Activity CNPC_Controller::NPC_TranslateActivity( Activity eNewActivity )
+{
+ switch ( eNewActivity)
+ {
+ case ACT_IDLE:
+ return (Activity)LookupFloat();
+ break;
+
+ default:
+ return BaseClass::NPC_TranslateActivity( eNewActivity );
+ }
+}
+
+//=========================================================
+// SetActivity -
+//=========================================================
+void CNPC_Controller::SetActivity ( Activity NewActivity )
+{
+ BaseClass::SetActivity( NewActivity );
+ m_flGroundSpeed = 100;
+}
+
+//=========================================================
+// RunAI
+//=========================================================
+void CNPC_Controller::RunAI( void )
+{
+ BaseClass::RunAI();
+
+ Vector vecStart;
+ QAngle angleGun;
+
+ //some kind of hack in hl1 ?
+// if ( HasMemory( bits_MEMORY_KILLED ) )
+ //use this instead
+ if( !IsAlive() )
+ return;
+
+ for (int i = 0; i < 2; i++)
+ {
+ if (m_pBall[i] == NULL)
+ {
+ m_pBall[i] = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), TRUE );
+ m_pBall[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_pBall[i]->SetAttachment( this, (i + 3) );
+ m_pBall[i]->SetScale( 1.0 );
+ }
+
+ float t = m_iBallTime[i] - gpGlobals->curtime;
+ if (t > 0.1)
+ t = 0.1 / t;
+ else
+ t = 1.0;
+
+ m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t;
+
+ m_pBall[i]->SetBrightness( m_iBallCurrent[i] );
+
+ GetAttachment( i + 2, vecStart, angleGun );
+ m_pBall[i]->SetAbsOrigin( vecStart );
+
+ CBroadcastRecipientFilter filter;
+ GetAttachment( i + 3, vecStart, angleGun );
+ te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0/*exponent*/, m_iBallCurrent[i] / 8 /*radius*/, 0.5, 0 );
+ }
+}
+
+//=========================================================
+// Stop -
+//=========================================================
+void CNPC_Controller::Stop( void )
+{
+ SetIdealActivity( GetStoppedActivity() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles movement towards the last move target.
+// Input : flInterval -
+//-----------------------------------------------------------------------------
+bool CNPC_Controller::OverridePathMove( float flInterval )
+{
+ CBaseEntity *pMoveTarget = (GetTarget()) ? GetTarget() : GetEnemy();
+ Vector waypointDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin();
+
+ float flWaypointDist = waypointDir.Length2D();
+ VectorNormalize(waypointDir);
+
+ // cut corner?
+ if (flWaypointDist < 128)
+ {
+ if (m_flGroundSpeed > 100)
+ m_flGroundSpeed -= 40;
+ }
+ else
+ {
+ if (m_flGroundSpeed < 400)
+ m_flGroundSpeed += 10;
+ }
+
+ m_velocity = m_velocity * 0.8 + m_flGroundSpeed * waypointDir * 0.5;
+ SetAbsVelocity( m_velocity );
+
+ // -----------------------------------------------------------------
+ // Check route is blocked
+ // ------------------------------------------------------------------
+ Vector checkPos = GetLocalOrigin() + (waypointDir * (m_flGroundSpeed * flInterval));
+
+ AIMoveTrace_t moveTrace;
+ GetMoveProbe()->MoveLimit( NAV_FLY, GetLocalOrigin(), checkPos, MASK_NPCSOLID|CONTENTS_WATER,
+ pMoveTarget, &moveTrace);
+ if (IsMoveBlocked( moveTrace ))
+ {
+ TaskFail(FAIL_NO_ROUTE);
+ GetNavigator()->ClearGoal();
+ return true;
+ }
+
+ // ----------------------------------------------
+
+ Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin();
+
+ if ( ProgressFlyPath( flInterval, pMoveTarget, MASK_NPCSOLID, false, 64 ) == AINPP_COMPLETE )
+ {
+ {
+ m_vLastPatrolDir = lastPatrolDir;
+ VectorNormalize(m_vLastPatrolDir);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CNPC_Controller::OverrideMove( float flInterval )
+{
+ if (m_flGroundSpeed == 0)
+ {
+ m_flGroundSpeed = 100;
+ }
+
+ // ----------------------------------------------
+ // Select move target
+ // ----------------------------------------------
+ CBaseEntity *pMoveTarget = NULL;
+ if (GetTarget() != NULL )
+ {
+ pMoveTarget = GetTarget();
+ }
+ else if (GetEnemy() != NULL )
+ {
+ pMoveTarget = GetEnemy();
+ }
+
+ // ----------------------------------------------
+ // Select move target position
+ // ----------------------------------------------
+ Vector vMoveTargetPos(0,0,0);
+ if (GetTarget())
+ {
+ vMoveTargetPos = GetTarget()->GetAbsOrigin();
+ }
+ else if (GetEnemy() != NULL)
+ {
+ vMoveTargetPos = GetEnemy()->GetAbsOrigin();
+ }
+
+ // -----------------------------------------
+ // See if we can fly there directly
+ // -----------------------------------------
+ if (pMoveTarget /*|| HaveInspectTarget()*/)
+ {
+ trace_t tr;
+
+ if (pMoveTarget)
+ {
+ UTIL_TraceEntity( this, GetAbsOrigin(), vMoveTargetPos,
+ MASK_NPCSOLID_BRUSHONLY, pMoveTarget, GetCollisionGroup(), &tr);
+ }
+ else
+ {
+ UTIL_TraceEntity( this, GetAbsOrigin(), vMoveTargetPos, MASK_NPCSOLID_BRUSHONLY, &tr);
+ }
+/*
+ float fTargetDist = (1-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();
+ if (fTargetDist > 50)
+ {
+ //SetCondition( COND_SCANNER_FLY_BLOCKED );
+ }
+ else
+ {
+ //SetCondition( COND_SCANNER_FLY_CLEAR );
+ }
+*/
+ }
+
+ // -----------------------------------------------------------------
+ // If I have a route, keep it updated and move toward target
+ // ------------------------------------------------------------------
+ if (GetNavigator()->IsGoalActive())
+ {
+ if ( OverridePathMove( flInterval ) )
+ return true;
+ }
+ else
+ {
+ //do nothing
+ Stop();
+ TaskComplete();
+ }
+
+ return true;
+}
+
+void CNPC_Controller::MoveToTarget( float flInterval, const Vector &vecMoveTarget )
+{
+ const float myAccel = 300.0;
+ const float myDecay = 9.0;
+
+ //TurnHeadToTarget( flInterval, MoveTarget );
+ MoveToLocation( flInterval, vecMoveTarget, myAccel, (2 * myAccel), myDecay );
+}
+
+//=========================================================
+// Controller bouncy ball attack
+//=========================================================
+
+LINK_ENTITY_TO_CLASS( controller_head_ball, CNPC_ControllerHeadBall );
+
+BEGIN_DATADESC( CNPC_ControllerHeadBall )
+
+ DEFINE_THINKFUNC( HuntThink ),
+ DEFINE_THINKFUNC( KillThink ),
+ DEFINE_ENTITYFUNC( BounceTouch ),
+
+ DEFINE_FIELD( m_pSprite, FIELD_CLASSPTR ),
+
+ DEFINE_FIELD( m_flSpawnTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vecIdeal, FIELD_VECTOR ),
+ DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
+
+END_DATADESC()
+
+
+void CNPC_ControllerHeadBall::Spawn( void )
+{
+ Precache( );
+ // motor
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+ SetSize( vec3_origin, vec3_origin );
+
+ m_pSprite = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), FALSE );
+ m_pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_pSprite->SetAttachment( this, 0 );
+ m_pSprite->SetScale( 2.0 );
+
+ UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
+ UTIL_SetOrigin( this, GetAbsOrigin() );
+
+ SetThink( &CNPC_ControllerHeadBall::HuntThink );
+ SetTouch( &CNPC_ControllerHeadBall::BounceTouch );
+
+// m_vecIdeal = vec3_origin; //(0,0,0)
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ m_hOwner = GetOwnerEntity();
+
+ m_flSpawnTime = gpGlobals->curtime;
+}
+
+
+void CNPC_ControllerHeadBall::Precache( void )
+{
+ PrecacheModel( "sprites/xspark4.vmt");
+}
+
+extern short g_sModelIndexLaser;
+
+void CNPC_ControllerHeadBall::HuntThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ if( !m_pSprite )
+ {
+ Assert(0);
+ return;
+ }
+
+ m_pSprite->SetBrightness( m_pSprite->GetBrightness() - 5, 0.1f );
+
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 255, 255, 255, 0, m_pSprite->GetBrightness() / 16, 0.2, 0 );
+
+ // check world boundaries
+ if (gpGlobals->curtime - m_flSpawnTime > 5 || m_pSprite->GetBrightness() < 64 /*|| GetEnemy() == NULL || m_hOwner == NULL*/ || !IsInWorld() )
+ {
+ SetTouch( NULL );
+ SetThink( &CNPC_ControllerHeadBall::KillThink );
+ SetNextThink( gpGlobals->curtime );
+ return;
+ }
+
+ if( !GetEnemy() )
+ return;
+
+ MovetoTarget( GetEnemy()->GetAbsOrigin() );
+
+ if ((GetEnemy()->WorldSpaceCenter() - GetAbsOrigin()).Length() < 64)
+ {
+ trace_t tr;
+
+ UTIL_TraceLine( GetAbsOrigin(), GetEnemy()->WorldSpaceCenter(), MASK_ALL, this, COLLISION_GROUP_NONE, &tr );
+
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if (pEntity != NULL && pEntity->m_takedamage == DAMAGE_YES)
+ {
+ ClearMultiDamage( );
+ Vector dir = GetAbsVelocity();
+ VectorNormalize( dir );
+ CTakeDamageInfo info( this, this, sk_controller_dmgball.GetFloat(), DMG_SHOCK );
+ CalculateMeleeDamageForce( &info, dir, tr.endpos );
+ pEntity->DispatchTraceAttack( info, dir, &tr );
+ ApplyMultiDamage();
+
+ int haloindex = 0;
+ int fadelength = 0;
+ int amplitude = 0;
+ const Vector vecEnd = tr.endpos;
+ te->BeamEntPoint( filter, 0.0, entindex(), NULL, 0, &(tr.m_pEnt->GetAbsOrigin()),
+ g_sModelIndexLaser, haloindex /* no halo */, 0, 10, 3, 20, 20, fadelength,
+ amplitude, 255, 255, 255, 255, 10 );
+
+ }
+
+ UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin(), "Controller.ElectroSound", 0.5, SNDLVL_NORM, 0, 100 );
+
+ SetNextAttack( gpGlobals->curtime + 3.0 );
+
+ SetThink( &CNPC_ControllerHeadBall::KillThink );
+ SetNextThink( gpGlobals->curtime + 0.3 );
+ }
+}
+
+void CNPC_ControllerHeadBall::MovetoTarget( Vector vecTarget )
+{
+ // accelerate
+ float flSpeed = m_vecIdeal.Length();
+ if (flSpeed == 0)
+ {
+ m_vecIdeal = GetAbsVelocity();
+ flSpeed = m_vecIdeal.Length();
+ }
+
+ if (flSpeed > 400)
+ {
+ VectorNormalize( m_vecIdeal );
+ m_vecIdeal = m_vecIdeal * 400;
+ }
+
+ Vector t = vecTarget - GetAbsOrigin();
+ VectorNormalize(t);
+ m_vecIdeal = m_vecIdeal + t * 100;
+ SetAbsVelocity(m_vecIdeal);
+}
+
+void CNPC_ControllerHeadBall::BounceTouch( CBaseEntity *pOther )
+{
+ Vector vecDir = m_vecIdeal;
+ VectorNormalize( vecDir );
+
+ trace_t tr;
+ tr = CBaseEntity::GetTouchTrace( );
+
+ float n = -DotProduct(tr.plane.normal, vecDir);
+
+ vecDir = 2.0 * tr.plane.normal * n + vecDir;
+
+ m_vecIdeal = vecDir * m_vecIdeal.Length();
+}
+
+void CNPC_ControllerHeadBall::KillThink( void )
+{
+ UTIL_Remove( m_pSprite );
+ UTIL_Remove( this );
+}
+
+
+//=========================================================
+// Controller Zap attack
+//=========================================================
+
+LINK_ENTITY_TO_CLASS( controller_energy_ball, CNPC_ControllerZapBall );
+
+BEGIN_DATADESC( CNPC_ControllerZapBall )
+
+ DEFINE_THINKFUNC( AnimateThink ),
+ DEFINE_ENTITYFUNC( ExplodeTouch ),
+
+ DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flSpawnTime, FIELD_TIME ),
+ DEFINE_FIELD( m_pSprite, FIELD_CLASSPTR ),
+
+END_DATADESC()
+
+
+void CNPC_ControllerZapBall::Spawn( void )
+{
+ Precache( );
+ // motor
+ SetMoveType( MOVETYPE_FLY );
+// SetSolid( SOLID_CUSTOM );
+ SetSolid( SOLID_BBOX );
+ SetSize( vec3_origin, vec3_origin );
+
+ m_pSprite = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), FALSE );
+ m_pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_pSprite->SetAttachment( this, 0 );
+ m_pSprite->SetScale( 0.5 );
+
+ UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
+ UTIL_SetOrigin( this, GetAbsOrigin() );
+
+ SetThink( &CNPC_ControllerZapBall::AnimateThink );
+ SetTouch( &CNPC_ControllerZapBall::ExplodeTouch );
+
+ m_hOwner = GetOwnerEntity();
+
+ m_flSpawnTime = gpGlobals->curtime; // keep track of when ball spawned
+ SetNextThink( gpGlobals->curtime + 0.1 );
+}
+
+
+void CNPC_ControllerZapBall::Precache( void )
+{
+ PrecacheModel( "sprites/xspark4.vmt");
+}
+
+
+void CNPC_ControllerZapBall::AnimateThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ SetCycle( ((int)GetCycle() + 1) % 11 );
+
+ if (gpGlobals->curtime - m_flSpawnTime > 5 || GetAbsVelocity().Length() < 10)
+ {
+ SetTouch( NULL );
+ Kill();
+ }
+}
+
+
+void CNPC_ControllerZapBall::ExplodeTouch( CBaseEntity *pOther )
+{
+ if (m_takedamage = DAMAGE_YES )
+ {
+ trace_t tr;
+ tr = GetTouchTrace( );
+
+ ClearMultiDamage( );
+
+ Vector vecAttackDir = GetAbsVelocity();
+ VectorNormalize( vecAttackDir );
+
+ if (m_hOwner != NULL)
+ {
+ CTakeDamageInfo info( this, m_hOwner, sk_controller_dmgball.GetFloat(), DMG_ENERGYBEAM );
+ CalculateMeleeDamageForce( &info, vecAttackDir, tr.endpos );
+ pOther->DispatchTraceAttack( info, vecAttackDir, &tr );
+ }
+ else
+ {
+ CTakeDamageInfo info( this, this, sk_controller_dmgball.GetFloat(), DMG_ENERGYBEAM );
+ CalculateMeleeDamageForce( &info, vecAttackDir, tr.endpos );
+ pOther->DispatchTraceAttack( info, vecAttackDir, &tr );
+ }
+
+ ApplyMultiDamage();
+
+ // void UTIL_EmitAmbientSound( CBaseEntity *entity, const Vector &vecOrigin, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float soundtime /*= 0.0f*/ )
+
+ UTIL_EmitAmbientSound( GetSoundSourceIndex(), tr.endpos, "Controller.ElectroSound", 0.3, SNDLVL_NORM, 0, random->RandomInt( 90, 99 ) );
+ }
+
+ Kill();
+}
+
+void CNPC_ControllerZapBall::Kill( void )
+{
+ UTIL_Remove( m_pSprite );
+ UTIL_Remove( this );
+}
diff --git a/game/server/hl1/hl1_npc_controller.h b/game/server/hl1/hl1_npc_controller.h
new file mode 100644
index 0000000..c012063
--- /dev/null
+++ b/game/server/hl1/hl1_npc_controller.h
@@ -0,0 +1,170 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_CONTROLLER_H
+#define NPC_CONTROLLER_H
+#pragma once
+
+#include "ai_basenpc_flyer.h"
+
+class CSprite;
+class CNPC_Controller;
+
+enum
+{
+ TASK_CONTROLLER_CHASE_ENEMY = LAST_SHARED_TASK,
+ TASK_CONTROLLER_STRAFE,
+ TASK_CONTROLLER_TAKECOVER,
+ TASK_CONTROLLER_FAIL,
+};
+
+enum
+{
+ SCHED_CONTROLLER_CHASE_ENEMY = LAST_SHARED_SCHEDULE,
+ SCHED_CONTROLLER_STRAFE,
+ SCHED_CONTROLLER_TAKECOVER,
+ SCHED_CONTROLLER_FAIL,
+};
+
+class CControllerNavigator : public CAI_ComponentWithOuter<CNPC_Controller, CAI_Navigator>
+{
+ typedef CAI_ComponentWithOuter<CNPC_Controller, CAI_Navigator> BaseClass;
+public:
+ CControllerNavigator( CNPC_Controller *pOuter )
+ : BaseClass( pOuter )
+ {
+ }
+
+ bool ActivityIsLocomotive( Activity activity ) { return true; }
+};
+
+class CNPC_Controller : public CAI_BaseFlyingBot
+{
+public:
+
+ DECLARE_CLASS( CNPC_Controller, CAI_BaseFlyingBot );
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ float MaxYawSpeed( void ) { return 120.0f; }
+ Class_T Classify ( void ) { return CLASS_ALIEN_MILITARY; }
+
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ void RunAI( void );
+
+ int RangeAttack1Conditions ( float flDot, float flDist ); // balls
+ int RangeAttack2Conditions ( float flDot, float flDist ); // head
+ int MeleeAttack1Conditions ( float flDot, float flDist ) { return COND_NONE; }
+ int MeleeAttack2Conditions ( float flDot, float flDist ) { return COND_NONE; }
+
+ int TranslateSchedule( int scheduleType );
+ void StartTask ( const Task_t *pTask );
+ void RunTask ( const Task_t *pTask );
+
+ void Stop( void );
+ bool OverridePathMove( float flInterval );
+ bool OverrideMove( float flInterval );
+
+ void MoveToTarget( float flInterval, const Vector &vecMoveTarget );
+
+ void SetActivity ( Activity NewActivity );
+ bool ShouldAdvanceRoute( float flWaypointDist );
+ int LookupFloat( );
+
+ friend class CControllerNavigator;
+ CAI_Navigator *CreateNavigator()
+ {
+ return new CControllerNavigator( this );
+ }
+
+ bool ShouldGib( const CTakeDamageInfo &info );
+ bool HasAlienGibs( void ) { return true; }
+ bool HasHumanGibs( void ) { return false; }
+
+ float m_flNextFlinch;
+
+ float m_flShootTime;
+ float m_flShootEnd;
+
+ void PainSound( void );
+ void AlertSound( void );
+ void IdleSound( void );
+ void AttackSound( void );
+ void DeathSound( void );
+
+ static const char *pAttackSounds[];
+ static const char *pIdleSounds[];
+ static const char *pAlertSounds[];
+ static const char *pPainSounds[];
+ static const char *pDeathSounds[];
+
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ CSprite *m_pBall[2]; // hand balls
+ int m_iBall[2]; // how bright it should be
+ float m_iBallTime[2]; // when it should be that color
+ int m_iBallCurrent[2]; // current brightness
+
+ Vector m_vecEstVelocity;
+
+ Vector m_velocity;
+ bool m_fInCombat;
+
+ void SetSequence( int nSequence );
+};
+
+class CNPC_ControllerHeadBall : public CAI_BaseNPC
+{
+public:
+ DECLARE_CLASS( CNPC_ControllerHeadBall, CAI_BaseNPC );
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ void EXPORT HuntThink( void );
+ void EXPORT KillThink( void );
+ void EXPORT BounceTouch( CBaseEntity *pOther );
+ void MovetoTarget( Vector vecTarget );
+
+ int m_iTrail;
+ int m_flNextAttack;
+ float m_flSpawnTime;
+ Vector m_vecIdeal;
+ EHANDLE m_hOwner;
+
+ CSprite *m_pSprite;
+};
+
+class CNPC_ControllerZapBall : public CAI_BaseNPC
+{
+public:
+ DECLARE_CLASS( CNPC_ControllerHeadBall, CAI_BaseNPC );
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ void EXPORT AnimateThink( void );
+ void EXPORT ExplodeTouch( CBaseEntity *pOther );
+
+ void Kill( void );
+
+ EHANDLE m_hOwner;
+ float m_flSpawnTime;
+
+ CSprite *m_pSprite;
+};
+
+#endif //NPC_CONTROLLER_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_gargantua.cpp b/game/server/hl1/hl1_npc_gargantua.cpp
new file mode 100644
index 0000000..c5d8b2c
--- /dev/null
+++ b/game/server/hl1/hl1_npc_gargantua.cpp
@@ -0,0 +1,1174 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
+// events
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "beam_shared.h"
+#include "Sprite.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 "hl1_npc_gargantua.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 "shake.h"
+#include "decals.h"
+#include "particle_smokegrenade.h"
+#include "gib.h"
+#include "func_break.h"
+#include "hl1_shareddefs.h"
+
+
+extern short g_sModelIndexFireball;
+int gGargGibModel;
+
+//=========================================================
+// Gargantua Monster
+//=========================================================
+#define GARG_ATTACKDIST 120.0f
+
+// Garg animation events
+#define GARG_AE_SLASH_LEFT 1
+//#define GARG_AE_BEAM_ATTACK_RIGHT 2 // No longer used
+#define GARG_AE_LEFT_FOOT 3
+#define GARG_AE_RIGHT_FOOT 4
+#define GARG_AE_STOMP 5
+#define GARG_AE_BREATHE 6
+
+
+// Gargantua is immune to any damage but this
+#define GARG_DAMAGE ( DMG_ENERGYBEAM | DMG_CRUSH | DMG_MISSILEDEFENSE | DMG_BLAST )
+#define GARG_EYE_SPRITE_NAME "sprites/gargeye1.vmt"
+#define GARG_BEAM_SPRITE_NAME "sprites/xbeam3.vmt"
+#define GARG_BEAM_SPRITE2 "sprites/xbeam3.vmt"
+#define GARG_STOMP_SPRITE_NAME "sprites/gargeye1.vmt"
+#define GARG_FLAME_LENGTH 330
+#define GARG_GIB_MODEL "models/metalplategibs.mdl"
+
+#define STOMP_SPRITE_COUNT 10
+
+
+#define ATTACH_EYE 1
+
+ConVar sk_gargantua_health ( "sk_gargantua_health", "800" );
+ConVar sk_gargantua_dmg_slash( "sk_gargantua_dmg_slash", "10" );
+ConVar sk_gargantua_dmg_fire ( "sk_gargantua_dmg_fire", "3" );
+ConVar sk_gargantua_dmg_stomp( "sk_gargantua_dmg_stomp", "50" );
+
+enum
+{
+ TASK_SOUND_ATTACK = LAST_SHARED_TASK,
+ TASK_FLAME_SWEEP,
+};
+
+enum
+{
+ SCHED_GARG_FLAME = LAST_SHARED_SCHEDULE,
+ SCHED_GARG_SWIPE,
+ SCHED_GARG_CHASE_ENEMY,
+ SCHED_GARG_CHASE_ENEMY_FAILED,
+};
+
+LINK_ENTITY_TO_CLASS( monster_gargantua, CNPC_Gargantua );
+
+BEGIN_DATADESC( CNPC_Gargantua )
+ DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_eyeBrightness, FIELD_INTEGER ),
+ DEFINE_FIELD( m_seeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flameTime, FIELD_TIME ),
+ DEFINE_FIELD( m_streakTime, FIELD_TIME ),
+ DEFINE_ARRAY( m_pFlame, FIELD_CLASSPTR, 4 ),
+ DEFINE_FIELD( m_flameX, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flameY, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flDmgTime, FIELD_TIME ),
+ DEFINE_FIELD( m_painSoundTime, FIELD_TIME ),
+END_DATADESC()
+
+static void MoveToGround( Vector *position, CBaseEntity *ignore, const Vector &mins, const Vector &maxs )
+{
+ trace_t tr;
+ // Find point on floor where enemy would stand at chasePosition
+ Vector floor = *position;
+ floor.z -= 1024;
+ UTIL_TraceHull( *position, floor, mins, maxs, MASK_NPCSOLID, ignore, COLLISION_GROUP_NONE, &tr );
+ if ( tr.fraction < 1 )
+ {
+ position->z = tr.endpos.z;
+ }
+}
+
+class CStomp : public CBaseEntity
+{
+ DECLARE_CLASS( CStomp, CBaseEntity );
+
+public:
+ DECLARE_DATADESC();
+
+ virtual void Precache();
+
+ void Spawn( void );
+ void Think( void );
+ static CStomp *StompCreate( Vector &origin, Vector &end, float speed, CBaseEntity* pOwner );
+
+private:
+ Vector m_vecMoveDir;
+ float m_flScale;
+ float m_flSpeed;
+ unsigned int m_uiFramerate;
+ float m_flDmgTime;
+ CBaseEntity* m_pOwner;
+
+// UNDONE: re-use this sprite list instead of creating new ones all the time
+// CSprite *m_pSprites[ STOMP_SPRITE_COUNT ];
+};
+
+BEGIN_DATADESC(CStomp)
+ DEFINE_FIELD( m_vecMoveDir, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flScale, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_uiFramerate, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flDmgTime, FIELD_TIME ),
+ DEFINE_FIELD( m_pOwner, FIELD_CLASSPTR ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( garg_stomp, CStomp );
+CStomp *CStomp::StompCreate( Vector &origin, Vector &end, float speed, CBaseEntity* pOwner )
+{
+ CStomp *pStomp = (CStomp*)CreateEntityByName( "garg_stomp" );
+
+ pStomp->SetAbsOrigin( origin );
+ Vector dir = (end - origin);
+// pStomp->m_flScale = dir.Length();
+ pStomp->m_flScale = 2048;
+ pStomp->m_vecMoveDir = dir;
+ VectorNormalize( pStomp->m_vecMoveDir );
+ pStomp->m_flSpeed = speed;
+ pStomp->m_pOwner = pOwner;
+ pStomp->Spawn();
+
+ return pStomp;
+
+}
+
+void CStomp::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "Garg.Stomp" );
+ PrecacheModel( GARG_STOMP_SPRITE_NAME );
+}
+
+
+void CStomp::Spawn( void )
+{
+ Precache();
+
+ SetNextThink( gpGlobals->curtime );
+ SetClassname( "garg_stomp" );
+ m_flDmgTime = gpGlobals->curtime;
+
+ m_uiFramerate = 30;
+// pev->rendermode = kRenderTransTexture;
+// SetBrightness( 0 );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Garg.Stomp" );
+
+}
+
+
+#define STOMP_INTERVAL 0.025
+
+void CStomp::Think( void )
+{
+ trace_t tr;
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ // Do damage for this frame
+ Vector vecStart = GetAbsOrigin();
+ vecStart.z += 30;
+ Vector vecEnd = vecStart + (m_vecMoveDir * m_flSpeed * gpGlobals->frametime);
+
+ UTIL_TraceHull( vecStart, vecEnd, Vector(-32, -32, -32), Vector(32, 32, 32), MASK_SOLID, m_pOwner, COLLISION_GROUP_NONE, &tr );
+// NDebugOverlay::Line( vecStart, vecEnd, 0, 255, 0, false, 10.0f );
+
+ if ( tr.m_pEnt )
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+ CTakeDamageInfo info( this, this, 50, DMG_SONIC );
+ CalculateMeleeDamageForce( &info, m_vecMoveDir, tr.endpos );
+ pEntity->TakeDamage( info );
+ }
+
+ // Accelerate the effect
+ m_flSpeed += (gpGlobals->frametime) * m_uiFramerate;
+ m_uiFramerate += (gpGlobals->frametime) * 2000;
+
+ // Move and spawn trails
+ if ( gpGlobals->curtime - m_flDmgTime > 0.2f )
+ {
+ m_flDmgTime = gpGlobals->curtime - 0.2f;
+ }
+
+ while ( gpGlobals->curtime - m_flDmgTime > STOMP_INTERVAL )
+ {
+ SetAbsOrigin( GetAbsOrigin() + m_vecMoveDir * m_flSpeed * STOMP_INTERVAL );
+ for ( int i = 0; i < 2; i++ )
+ {
+ CSprite *pSprite = CSprite::SpriteCreate( GARG_STOMP_SPRITE_NAME, GetAbsOrigin(), TRUE );
+ if ( pSprite )
+ {
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector(0,0,500), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ pSprite->SetAbsOrigin( tr.endpos );
+// pSprite->pev->velocity = Vector(RandomFloat(-200,200),RandomFloat(-200,200),175);
+ pSprite->SetNextThink( gpGlobals->curtime + 0.3 );
+ pSprite->SetThink( &CSprite::SUB_Remove );
+ pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxFadeFast );
+ }
+ g_pEffects->EnergySplash( tr.endpos, tr.plane.normal );
+ }
+ m_flDmgTime += STOMP_INTERVAL;
+ // Scale has the "life" of this effect
+ m_flScale -= STOMP_INTERVAL * m_flSpeed;
+ if ( m_flScale <= 0 )
+ {
+ // Life has run out
+ UTIL_Remove(this);
+ CPASAttenuationFilter filter( this );
+ StopSound( entindex(), CHAN_STATIC, "Garg.Stomp" );
+ }
+ }
+}
+
+
+//=========================================================================
+// Gargantua
+//=========================================================================
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Gargantua::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/garg.mdl" );
+
+ SetNavType(NAV_GROUND);
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+
+ Vector vecSurroundingMins( -80, -80, 0 );
+ Vector vecSurroundingMaxs( 80, 80, 214 );
+ CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );
+
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ m_iHealth = sk_gargantua_health.GetFloat();
+ SetViewOffset( Vector ( 0, 0, 96 ) );// taken from mdl file
+ m_flFieldOfView = -0.2;// width of forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND );
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 );
+
+ SetHullType( HULL_LARGE );
+ SetHullSizeNormal();
+
+ m_pEyeGlow = CSprite::SpriteCreate( GARG_EYE_SPRITE_NAME, GetAbsOrigin(), FALSE );
+ m_pEyeGlow->SetTransparency( kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation );
+ m_pEyeGlow->SetAttachment( this, 1 );
+ EyeOff();
+
+ m_seeTime = gpGlobals->curtime + 5;
+ m_flameTime = gpGlobals->curtime + 2;
+
+ NPCInit();
+
+ BaseClass::Spawn();
+
+ // Give garg a healthy free knowledge.
+ GetEnemies()->SetFreeKnowledgeDuration( 59.0f );
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Gargantua::Precache()
+{
+ PrecacheModel("models/garg.mdl");
+ PrecacheModel( GARG_EYE_SPRITE_NAME );
+ PrecacheModel( GARG_BEAM_SPRITE_NAME );
+ PrecacheModel( GARG_BEAM_SPRITE2 );
+ //gStompSprite = PRECACHE_MODEL( GARG_STOMP_SPRITE_NAME );
+ gGargGibModel = PrecacheModel( GARG_GIB_MODEL );
+
+ PrecacheScriptSound( "Garg.AttackHit" );
+ PrecacheScriptSound( "Garg.AttackMiss" );
+ PrecacheScriptSound( "Garg.Footstep" );
+ PrecacheScriptSound( "Garg.Breath" );
+ PrecacheScriptSound( "Garg.Attack" );
+ PrecacheScriptSound( "Garg.Pain" );
+ PrecacheScriptSound( "Garg.BeamAttackOn" );
+ PrecacheScriptSound( "Garg.BeamAttackRun" );
+ PrecacheScriptSound( "Garg.BeamAttackOff" );
+ PrecacheScriptSound( "Garg.StompSound" );
+}
+
+
+Class_T CNPC_Gargantua::Classify ( void )
+{
+ return CLASS_ALIEN_MONSTER;
+}
+
+void CNPC_Gargantua::PrescheduleThink( void )
+{
+ if ( !HasCondition( COND_SEE_ENEMY ) )
+ {
+ m_seeTime = gpGlobals->curtime + 5;
+ EyeOff();
+ }
+ else
+ {
+ EyeOn( 200 );
+ }
+
+ EyeUpdate();
+}
+
+float CNPC_Gargantua::MaxYawSpeed ( void )
+{
+ float ys = 60;
+
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ ys = 60;
+ break;
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ ys = 180;
+ break;
+ case ACT_WALK:
+ case ACT_RUN:
+ ys = 60;
+ break;
+
+ default:
+ ys = 60;
+ break;
+ }
+
+ return ys;
+}
+
+int CNPC_Gargantua::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ if (flDot >= 0.7)
+ {
+ if ( flDist <= GARG_ATTACKDIST )
+ {
+ return COND_CAN_MELEE_ATTACK1;
+ }
+ }
+
+ return COND_NONE;
+}
+
+
+// Flame thrower madness!
+int CNPC_Gargantua::MeleeAttack2Conditions( float flDot, float flDist )
+{
+ if ( gpGlobals->curtime > m_flameTime )
+ {
+ if ( flDot >= 0.8 )
+ {
+ if ( flDist > GARG_ATTACKDIST )
+ {
+ if ( flDist <= GARG_FLAME_LENGTH )
+ return COND_CAN_MELEE_ATTACK2;
+ }
+ }
+ }
+
+ return COND_NONE;
+}
+
+//=========================================================
+// CheckRangeAttack1
+// flDot is the cos of the angle of the cone within which
+// the attack can occur.
+//=========================================================
+//
+// Stomp attack
+//
+//=========================================================
+int CNPC_Gargantua::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( gpGlobals->curtime > m_seeTime )
+ {
+ if ( flDot >= 0.7 )
+ {
+ if ( flDist > GARG_ATTACKDIST )
+ {
+ return COND_CAN_RANGE_ATTACK1;
+ }
+ }
+ }
+
+ return COND_NONE;
+}
+
+//=========================================================
+// CheckTraceHullAttack - expects a length to trace, amount
+// of damage to do, and damage type. Returns a pointer to
+// the damaged entity in case the monster wishes to do
+// other stuff to the victim (punchangle, etc)
+// Used for many contact-range melee attacks. Bites, claws, etc.
+
+// Overridden for Gargantua because his swing starts lower as
+// a percentage of his height (otherwise he swings over the
+// players head)
+//=========================================================
+CBaseEntity* CNPC_Gargantua::GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType)
+{
+ trace_t tr;
+
+ Vector vForward, vUp;
+ AngleVectors( GetAbsAngles(), &vForward, NULL, &vUp );
+
+ Vector vecStart = GetAbsOrigin();
+ vecStart.z += 64;
+ Vector vecEnd = vecStart + ( vForward * flDist) - ( vUp * flDist * 0.3);
+
+ //UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr );
+
+ UTIL_TraceEntity( this, GetAbsOrigin(), vecEnd, MASK_SOLID, &tr );
+
+ if ( tr.m_pEnt )
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+
+ if ( iDamage > 0 )
+ {
+ CTakeDamageInfo info( this, this, iDamage, iDmgType );
+ CalculateMeleeDamageForce( &info, vForward, tr.endpos );
+ pEntity->TakeDamage( info );
+ }
+
+ return pEntity;
+ }
+
+ return NULL;
+}
+
+void CNPC_Gargantua::HandleAnimEvent( animevent_t *pEvent )
+{
+ CPASAttenuationFilter filter( this );
+
+ switch( pEvent->event )
+ {
+ case GARG_AE_SLASH_LEFT:
+ {
+ // HACKHACK!!!
+ CBaseEntity *pHurt = GargantuaCheckTraceHullAttack( GARG_ATTACKDIST + 10.0, sk_gargantua_dmg_slash.GetFloat(), DMG_SLASH );
+
+ if (pHurt)
+ {
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ {
+ pHurt->ViewPunch( QAngle( -30, -30, 30 ) );
+
+ Vector vRight;
+ AngleVectors( GetAbsAngles(), NULL, &vRight, NULL );
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - vRight * 100 );
+ }
+
+ EmitSound( filter, entindex(), "Garg.AttackHit" );
+ }
+ else // Play a random attack miss sound
+ {
+ EmitSound( filter, entindex(),"Garg.AttackMiss" );
+ }
+ }
+ break;
+
+ case GARG_AE_RIGHT_FOOT:
+ case GARG_AE_LEFT_FOOT:
+
+ UTIL_ScreenShake( GetAbsOrigin(), 4.0, 3.0, 1.0, 1500, SHAKE_START );
+ EmitSound( filter, entindex(), "Garg.Footstep" );
+ break;
+
+ case GARG_AE_STOMP:
+ StompAttack();
+ m_seeTime = gpGlobals->curtime + 12;
+ break;
+
+ case GARG_AE_BREATHE:
+ EmitSound( filter, entindex(), "Garg.Breath" );
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent(pEvent);
+ break;
+ }
+}
+
+int CNPC_Gargantua::TranslateSchedule( int scheduleType )
+{
+ //TEMP TEMP
+ if ( FlameIsOn() )
+ FlameDestroy();
+
+ switch( scheduleType )
+ {
+ case SCHED_MELEE_ATTACK2:
+ return SCHED_GARG_FLAME;
+ case SCHED_MELEE_ATTACK1:
+ return SCHED_GARG_SWIPE;
+
+ case SCHED_CHASE_ENEMY:
+ return SCHED_GARG_CHASE_ENEMY;
+
+ case SCHED_CHASE_ENEMY_FAILED:
+ return SCHED_GARG_CHASE_ENEMY_FAILED;
+
+ case SCHED_ALERT_STAND:
+ return SCHED_CHASE_ENEMY;
+
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+void CNPC_Gargantua::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_FLAME_SWEEP:
+
+ //TEMP TEMP
+ FlameCreate();
+ m_flWaitFinished = gpGlobals->curtime + pTask->flTaskData;
+ m_flameTime = gpGlobals->curtime + 6;
+ m_flameX = 0;
+ m_flameY = 0;
+ break;
+
+ case TASK_SOUND_ATTACK:
+
+ if ( random->RandomInt(0,100) < 30 )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Garg.Attack" );
+ }
+
+ TaskComplete();
+ break;
+
+ case TASK_DIE:
+ m_flWaitFinished = gpGlobals->curtime + 1.6;
+ DeathEffect();
+ // FALL THROUGH
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+bool CNPC_Gargantua::ShouldGib( const CTakeDamageInfo &info )
+{
+ return false;
+}
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_Gargantua::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_DIE:
+
+ if ( gpGlobals->curtime > m_flWaitFinished )
+ {
+ //TEMP TEMP
+ m_nRenderFX = kRenderFxExplode;
+ SetRenderColor( 255, 0, 0 , 255 );
+ StopAnimation();
+ SetNextThink( gpGlobals->curtime + 0.15 );
+ SetThink( &CBaseEntity::SUB_Remove );
+
+ int i;
+
+ int parts = modelinfo->GetModelFrameCount( modelinfo->GetModel( gGargGibModel ) );
+
+ for ( i = 0; i < 10; i++ )
+ {
+ CGib *pGib = CREATE_ENTITY( CGib, "gib" );
+
+ pGib->Spawn( GARG_GIB_MODEL);
+
+ int bodyPart = 0;
+
+ if ( parts > 1 )
+ bodyPart = random->RandomInt( 0, parts-1 );
+
+ pGib->SetBodygroup( 0, bodyPart );
+ pGib->SetBloodColor( BLOOD_COLOR_YELLOW );
+ pGib->m_material = matNone;
+ pGib->SetAbsOrigin( GetAbsOrigin() );
+ pGib->SetAbsVelocity( UTIL_RandomBloodVector() * random->RandomFloat( 300, 500 ) );
+
+ pGib->SetNextThink( gpGlobals->curtime + 1.25 );
+ pGib->SetThink( &CBaseEntity::SUB_FadeOut );
+ }
+
+ Vector vecSize = Vector( 200, 200, 128 );
+ CPVSFilter filter( GetAbsOrigin() );
+ te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle, vecSize, vec3_origin,
+ gGargGibModel, 200, 50, 3.0, BREAK_FLESH );
+
+ return;
+ }
+ else
+ BaseClass::RunTask( pTask );
+ break;
+
+ case TASK_FLAME_SWEEP:
+ if ( gpGlobals->curtime > m_flWaitFinished )
+ {
+ //TEMP TEMP
+ FlameDestroy();
+ TaskComplete();
+ FlameControls( 0, 0 );
+ SetBoneController( 0, 0 );
+ SetBoneController( 1, 0 );
+ }
+ else
+ {
+ bool cancel = false;
+
+ QAngle angles = QAngle( 0, 0, 0 );
+
+ //TEMP TEMP
+ FlameUpdate();
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( pEnemy )
+ {
+ Vector org = GetAbsOrigin();
+ org.z += 64;
+ Vector dir = pEnemy->BodyTarget(org) - org;
+
+ VectorAngles( dir, angles );
+ angles.x = -angles.x;
+ angles.y -= GetAbsAngles().y;
+
+ if ( dir.Length() > 400 )
+ cancel = true;
+ }
+ if ( fabs(angles.y) > 60 )
+ cancel = true;
+
+ if ( cancel )
+ {
+ m_flWaitFinished -= 0.5;
+ m_flameTime -= 0.5;
+ }
+
+ //TEMP TEMP
+ //FlameControls( angles.x + 2 * sin(gpGlobals->curtime*8), angles.y + 28 * sin(gpGlobals->curtime*8.5) );
+ FlameControls( angles.x, angles.y );
+ }
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+void CNPC_Gargantua::FlameCreate( void )
+{
+ int i;
+ Vector posGun;
+ QAngle angleGun;
+
+ trace_t trace;
+
+ Vector vForward;
+
+ AngleVectors( GetAbsAngles(), &vForward );
+
+ for ( i = 0; i < 4; i++ )
+ {
+ if ( i < 2 )
+ m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE_NAME, 24.0 );
+ else
+ m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE2, 14.0 );
+ if ( m_pFlame[i] )
+ {
+ int attach = i%2;
+ // attachment is 0 based in GetAttachment
+ GetAttachment( attach+1, posGun, angleGun );
+
+ Vector vecEnd = ( vForward * GARG_FLAME_LENGTH) + posGun;
+ //UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &trace );
+
+ UTIL_TraceLine ( posGun, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace);
+// NDebugOverlay::Line( posGun, vecEnd, 255, 255, 255, false, 10.0f );
+
+ m_pFlame[i]->PointEntInit( trace.endpos, this );
+ if ( i < 2 )
+ m_pFlame[i]->SetColor( 255, 130, 90 );
+ else
+ m_pFlame[i]->SetColor( 0, 120, 255 );
+ m_pFlame[i]->SetBrightness( 190 );
+ m_pFlame[i]->SetBeamFlags( FBEAM_SHADEIN );
+ m_pFlame[i]->SetScrollRate( 20 );
+ // attachment is 1 based in SetEndAttachment
+ m_pFlame[i]->SetEndAttachment( attach + 2 );
+ CSoundEnt::InsertSound( SOUND_COMBAT, posGun, 384, 0.3 );
+ }
+ }
+
+ CPASAttenuationFilter filter4( this );
+ EmitSound( filter4, entindex(), "Garg.BeamAttackOn" );
+ EmitSound( filter4, entindex(), "Garg.BeamAttackRun" );
+}
+
+
+void CNPC_Gargantua::FlameControls( float angleX, float angleY )
+{
+ if ( angleY < -180 )
+ angleY += 360;
+ else if ( angleY > 180 )
+ angleY -= 360;
+
+ if ( angleY < -45 )
+ angleY = -45;
+ else if ( angleY > 45 )
+ angleY = 45;
+
+ m_flameX = UTIL_ApproachAngle( angleX, m_flameX, 4 );
+ m_flameY = UTIL_ApproachAngle( angleY, m_flameY, 8 );
+ SetBoneController( 0, m_flameY );
+ SetBoneController( 1, m_flameX );
+}
+
+
+void CNPC_Gargantua::FlameUpdate( void )
+{
+ int i;
+ static float offset[2] = { 60, -60 };
+ trace_t trace;
+ Vector vecStart;
+ QAngle angleGun;
+ BOOL streaks = FALSE;
+
+ Vector vForward;
+
+ for ( i = 0; i < 2; i++ )
+ {
+ if ( m_pFlame[i] )
+ {
+ QAngle vecAim = GetAbsAngles();
+ vecAim.x += -m_flameX;
+ vecAim.y += m_flameY;
+
+ AngleVectors( vecAim, &vForward );
+
+ GetAttachment( i + 2, vecStart, angleGun );
+ Vector vecEnd = vecStart + ( vForward * GARG_FLAME_LENGTH); // - offset[i] * gpGlobals->v_right;
+
+ UTIL_TraceLine ( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace);
+
+ m_pFlame[i]->SetStartPos( trace.endpos );
+ m_pFlame[i+2]->SetStartPos( (vecStart * 0.6) + (trace.endpos * 0.4) );
+
+ if ( trace.fraction != 1.0 && gpGlobals->curtime > m_streakTime )
+ {
+ g_pEffects->Sparks( trace.endpos, 1, 1, &trace.plane.normal );
+ streaks = TRUE;
+ UTIL_DecalTrace( &trace, "SmallScorch" );
+ }
+ // RadiusDamage( trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN );
+ FlameDamage( vecStart, trace.endpos, this, this, sk_gargantua_dmg_fire.GetFloat(), CLASS_ALIEN_MONSTER, DMG_BURN );
+
+ CBroadcastRecipientFilter filter;
+ GetAttachment(i + 2, vecStart, angleGun);
+ te->DynamicLight( filter, 0.0, &vecStart, 255, 0, 0, 0, 48, 0.2, 150 );
+ }
+ }
+ if ( streaks )
+ m_streakTime = gpGlobals->curtime;
+}
+
+
+
+void CNPC_Gargantua::FlameDamage( Vector vecStart, Vector vecEnd, CBaseEntity *pevInflictor, CBaseEntity *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType )
+{
+ CBaseEntity *pEntity = NULL;
+ trace_t tr;
+ float flAdjustedDamage;
+ Vector vecSpot;
+
+ Vector vecMid = (vecStart + vecEnd) * 0.5;
+
+// float searchRadius = (vecStart - vecMid).Length();
+ float searchRadius = GARG_FLAME_LENGTH;
+ float maxDamageRadius = searchRadius / 2.0;
+
+ Vector vecAim = (vecEnd - vecStart);
+
+ VectorNormalize( vecAim );
+
+ // iterate on all entities in the vicinity.
+ while ((pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), searchRadius )) != NULL)
+ {
+
+ if ( pEntity->m_takedamage != DAMAGE_NO )
+ {
+ // UNDONE: this should check a damage mask, not an ignore
+ if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore )
+ {// houndeyes don't hurt other houndeyes with their attack
+ continue;
+ }
+
+ vecSpot = pEntity->BodyTarget( vecMid );
+
+ float dist = DotProduct( vecAim, vecSpot - vecMid );
+ if (dist > searchRadius)
+ dist = searchRadius;
+ else if (dist < -searchRadius)
+ dist = searchRadius;
+
+ Vector vecSrc = vecMid + dist * vecAim;
+
+ UTIL_TraceLine ( vecStart, vecSpot, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+// NDebugOverlay::Line( vecStart, vecSpot, 0, 255, 0, false, 10.0f );
+
+ if ( tr.fraction == 1.0 || tr.m_pEnt == pEntity )
+ {// the explosion can 'see' this entity, so hurt them!
+ // decrease damage for an ent that's farther from the flame.
+ dist = ( vecSrc - tr.endpos ).Length();
+
+ if (dist > maxDamageRadius)
+ {
+ flAdjustedDamage = flDamage - (dist - maxDamageRadius) * 0.4;
+ if (flAdjustedDamage <= 0)
+ continue;
+ }
+ else
+ {
+ flAdjustedDamage = flDamage;
+ }
+
+ // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) );
+ if (tr.fraction != 1.0)
+ {
+ ClearMultiDamage( );
+
+ Vector vDir = (tr.endpos - vecSrc);
+ VectorNormalize( vDir );
+
+ CTakeDamageInfo info( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType );
+ CalculateMeleeDamageForce( &info, vDir, tr.endpos );
+ pEntity->DispatchTraceAttack( info, vDir, &tr );
+ ApplyMultiDamage();
+ }
+ else
+ {
+ pEntity->TakeDamage( CTakeDamageInfo( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ) );
+ }
+ }
+ }
+ }
+}
+
+
+void CNPC_Gargantua::FlameDestroy( void )
+{
+ int i;
+
+ CPASAttenuationFilter filter4( this );
+ EmitSound( filter4, entindex(), "Garg.BeamAttackOff" );
+
+ for ( i = 0; i < 4; i++ )
+ {
+ if ( m_pFlame[i] )
+ {
+ UTIL_Remove( m_pFlame[i] );
+ m_pFlame[i] = NULL;
+ }
+ }
+}
+
+
+void CNPC_Gargantua::EyeOn( int level )
+{
+ m_eyeBrightness = level;
+}
+
+void CNPC_Gargantua::EyeOff( void )
+{
+ m_eyeBrightness = 0;
+}
+
+void CNPC_Gargantua::EyeUpdate( void )
+{
+ if ( m_pEyeGlow )
+ {
+ m_pEyeGlow->SetBrightness( UTIL_Approach( m_eyeBrightness, m_pEyeGlow->GetBrightness(), 26 ), 0.5f );
+ if ( m_pEyeGlow->GetBrightness() == 0 )
+ {
+ m_pEyeGlow->AddEffects( EF_NODRAW );
+ }
+ else
+ {
+ m_pEyeGlow->RemoveEffects( EF_NODRAW );
+ }
+ }
+}
+
+
+void CNPC_Gargantua::StompAttack( void )
+{
+ trace_t trace;
+
+ Vector vecForward;
+ AngleVectors(GetAbsAngles(), &vecForward );
+ Vector vecStart = GetAbsOrigin() + Vector(0,0,60) + 35 * vecForward;
+
+ CBaseEntity* pPlayer = GetEnemy();
+ if ( !pPlayer )
+ return;
+
+ Vector vecAim = pPlayer->GetAbsOrigin() - GetAbsOrigin();
+ VectorNormalize( vecAim );
+ Vector vecEnd = (vecAim * 1024) + vecStart;
+
+ UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
+// NDebugOverlay::Line( vecStart, vecEnd, 255, 0, 0, false, 10.0f );
+
+ CStomp::StompCreate( vecStart, trace.endpos, 0, this );
+ UTIL_ScreenShake( GetAbsOrigin(), 12.0, 100.0, 2.0, 1000, SHAKE_START );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Garg.StompSound" );
+
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector(0,0,20), MASK_SOLID, this, COLLISION_GROUP_NONE, &trace );
+ if ( trace.fraction < 1.0 )
+ {
+ UTIL_DecalTrace( &trace, "SmallScorch" );
+ }
+}
+
+void CNPC_Gargantua::DeathEffect( void )
+{
+ int i;
+
+ Vector vForward;
+ AngleVectors( GetAbsAngles(), &vForward );
+ Vector deathPos = GetAbsOrigin() + vForward * 100;
+
+ Vector position = GetAbsOrigin();
+ position.z += 32;
+
+ CPASFilter filter( GetAbsOrigin() );
+ for ( i = 0; i < 7; i++)
+ {
+ te->Explosion( filter, i * 0.2, &GetAbsOrigin(), g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0 );
+ position.z += 15;
+ }
+
+ UTIL_Smoke(GetAbsOrigin(),random->RandomInt(10, 15), 10);
+ UTIL_ScreenShake( GetAbsOrigin(), 25.0, 100.0, 5.0, 1000, SHAKE_START );
+
+}
+
+void CNPC_Gargantua::Event_Killed( const CTakeDamageInfo &info )
+{
+ EyeOff();
+ UTIL_Remove( m_pEyeGlow );
+ m_pEyeGlow = NULL;
+ BaseClass::Event_Killed( info );
+ m_takedamage = DAMAGE_NO;
+}
+
+void CNPC_Gargantua::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo subInfo = info;
+
+ if ( !IsAlive() )
+ {
+ BaseClass::TraceAttack( subInfo, vecDir, ptr, pAccumulator );
+ return;
+ }
+
+ // UNDONE: Hit group specific damage?
+ if ( subInfo.GetDamageType() & ( GARG_DAMAGE | DMG_BLAST ) )
+ {
+ if ( m_painSoundTime < gpGlobals->curtime )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Garg.Pain" );
+
+ m_painSoundTime = gpGlobals->curtime + random->RandomFloat( 2.5, 4 );
+ }
+ }
+
+ int bitsDamageType = subInfo.GetDamageType();
+
+ bitsDamageType &= GARG_DAMAGE;
+
+ subInfo.SetDamageType( bitsDamageType );
+
+ if ( subInfo.GetDamageType() == 0 )
+ {
+ if ( m_flDmgTime != gpGlobals->curtime || (random->RandomInt( 0, 100 ) < 20) )
+ {
+ g_pEffects->Ricochet(ptr->endpos, -vecDir );
+ m_flDmgTime = gpGlobals->curtime;
+ }
+
+ subInfo.SetDamage( 0 );
+ }
+
+ BaseClass::TraceAttack( subInfo, vecDir, ptr, pAccumulator );
+}
+
+int CNPC_Gargantua::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ if( GetState() == NPC_STATE_SCRIPT )
+ {
+ // Invulnerable while scripted. This fixes the problem where garg wouldn't
+ // explode in C2A1 because for some reason he wouldn't die while scripted, he'd
+ // only freeze in place. Now he's just immune until he gets to the script and stops.
+ return 0;
+ }
+
+ CTakeDamageInfo subInfo = info;
+
+ float flDamage = subInfo.GetDamage();
+
+ if ( IsAlive() )
+ {
+ if ( !(subInfo.GetDamageType() & GARG_DAMAGE) )
+ {
+ flDamage *= 0.01;
+ subInfo.SetDamage( flDamage );
+ }
+ if ( subInfo.GetDamageType() & DMG_BLAST )
+ {
+ SetCondition( COND_LIGHT_DAMAGE );
+ }
+ }
+
+ return BaseClass::OnTakeDamage_Alive( subInfo );
+}
+
+AI_BEGIN_CUSTOM_NPC( monster_gargantua, CNPC_Gargantua )
+
+DECLARE_TASK ( TASK_SOUND_ATTACK )
+DECLARE_TASK ( TASK_FLAME_SWEEP )
+
+ //=========================================================
+ // > SCHED_GARG_FLAME
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GARG_FLAME,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_SOUND_ATTACK 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_MELEE_ATTACK2"
+ " TASK_FLAME_SWEEP 4.5"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ )
+
+ //=========================================================
+ // > SCHED_GARG_SWIPE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GARG_SWIPE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_MELEE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_CAN_MELEE_ATTACK2"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_GARG_CHASE_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GARG_CHASE_ENEMY_FAILED"
+ " TASK_GET_CHASE_PATH_TO_ENEMY 300"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_ENEMY_UNREACHABLE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_TOO_CLOSE_TO_ATTACK"
+ " COND_LOST_ENEMY"
+ );
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_GARG_CHASE_ENEMY_FAILED,
+
+ " Tasks"
+ " TASK_SET_ROUTE_SEARCH_TIME 2" // Spend 2 seconds trying to build a path if stuck
+ " TASK_GET_PATH_TO_RANDOM_NODE 180"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK2"
+ );
+
+AI_END_CUSTOM_NPC()
diff --git a/game/server/hl1/hl1_npc_gargantua.h b/game/server/hl1/hl1_npc_gargantua.h
new file mode 100644
index 0000000..f86484e
--- /dev/null
+++ b/game/server/hl1/hl1_npc_gargantua.h
@@ -0,0 +1,86 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_GARGANTUA_H
+#define NPC_GARGANTUA_H
+
+#include "hl1_ai_basenpc.h"
+
+class CNPC_Gargantua : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Gargantua, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ Class_T Classify ( void );
+
+ float MaxYawSpeed ( void );
+
+ int MeleeAttack1Conditions( float flDot, float flDist );
+ int MeleeAttack2Conditions( float flDot, float flDist );
+ int RangeAttack1Conditions( float flDot, float flDist );
+
+ void HandleAnimEvent( animevent_t *pEvent );
+ int TranslateSchedule( int scheduleType );
+
+ void StartTask( const Task_t *pTask );
+ void RunTask ( const Task_t *pTask );
+
+ bool CanBecomeRagdoll() { return false; }
+
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+/* 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 );
+
+ Schedule_t *GetScheduleOfType( int Type );
+ void StartTask( Task_t *pTask );
+ void RunTask( Task_t *pTask );
+*/
+ void PrescheduleThink( void );
+ void Event_Killed( const CTakeDamageInfo &info );
+ void DeathEffect( void );
+
+ bool ShouldGib( const CTakeDamageInfo &info );
+
+ void EyeOff( void );
+ void EyeOn( int level );
+ void EyeUpdate( void );
+// void Leap( void );
+ void StompAttack( void );
+ void FlameCreate( void );
+ void FlameUpdate( void );
+ void FlameControls( float angleX, float angleY );
+ void FlameDestroy( void );
+ inline BOOL FlameIsOn( void ) { return m_pFlame[0] != NULL; }
+
+ void FlameDamage( Vector vecStart, Vector vecEnd, CBaseEntity *pevInflictor, CBaseEntity *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType );
+
+
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+
+private:
+ CBaseEntity* GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType);
+
+ CSprite *m_pEyeGlow; // Glow around the eyes
+ CBeam *m_pFlame[4]; // Flame beams
+
+ int m_eyeBrightness; // Brightness target
+ float m_seeTime; // Time to attack (when I see the enemy, I set this)
+ float m_flameTime; // Time of next flame attack
+ float m_painSoundTime; // Time of next pain sound
+ float m_streakTime; // streak timer (don't send too many)
+ float m_flameX; // Flame thrower aim
+ float m_flameY;
+
+ float m_flDmgTime;
+};
+
+#endif //NPC_GARGANTUA_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_gman.cpp b/game/server/hl1/hl1_npc_gman.cpp
new file mode 100644
index 0000000..5ea95cd
--- /dev/null
+++ b/game/server/hl1/hl1_npc_gman.cpp
@@ -0,0 +1,232 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+//=========================================================
+// GMan - misunderstood servant of the people
+//=========================================================
+
+#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 "IEffects.h"
+#include "vstdlib/random.h"
+#include "ai_baseactor.h"
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+
+class CNPC_GMan : public CAI_BaseActor
+{
+ DECLARE_CLASS( CNPC_GMan, CAI_BaseActor );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ float MaxYawSpeed( void ){ return 90.0f; }
+ Class_T Classify ( void );
+ void HandleAnimEvent( animevent_t *pEvent );
+ int GetSoundInterests ( void );
+
+ bool IsInC5A1();
+
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+ void TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType);
+
+ virtual int PlayScriptedSentence( const char *pszSentence, float duration, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener );
+
+ EHANDLE m_hPlayer;
+ EHANDLE m_hTalkTarget;
+ float m_flTalkTime;
+};
+
+LINK_ENTITY_TO_CLASS( monster_gman, CNPC_GMan );
+
+//=========================================================
+// Hack that tells us whether the GMan is in the final map
+//=========================================================
+bool CNPC_GMan::IsInC5A1()
+{
+ const char *pMapName = STRING(gpGlobals->mapname);
+
+ if( pMapName )
+ {
+ return !Q_strnicmp( pMapName, "c5a1", 4 );
+ }
+
+ return false;
+}
+
+//=========================================================
+// Classify - indicates this monster's place in the
+// relationship table.
+//=========================================================
+Class_T CNPC_GMan::Classify ( void )
+{
+ return CLASS_NONE;
+}
+
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_GMan::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case 1:
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+//=========================================================
+// GetSoundInterests - generic monster can't hear.
+//=========================================================
+int CNPC_GMan::GetSoundInterests ( void )
+{
+ return NULL;
+}
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_GMan::Spawn()
+{
+ Precache();
+
+ BaseClass::Spawn();
+
+ SetModel( "models/gman.mdl" );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ SetBloodColor( BLOOD_COLOR_MECH );
+ m_iHealth = 8;
+ m_flFieldOfView = 0.5;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_USE_WEAPONS | bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD);
+
+ NPCInit();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_GMan::Precache()
+{
+ PrecacheModel( "models/gman.mdl" );
+}
+
+
+//=========================================================
+// AI Schedules Specific to this monster
+//=========================================================
+
+
+void CNPC_GMan::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_WAIT:
+ if (m_hPlayer == NULL)
+ {
+ m_hPlayer = gEntList.FindEntityByClassname( NULL, "player" );
+ }
+ break;
+ }
+
+ BaseClass::StartTask( pTask );
+}
+
+void CNPC_GMan::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_WAIT:
+ // look at who I'm talking to
+ if (m_flTalkTime > gpGlobals->curtime && m_hTalkTarget != NULL)
+ {
+ AddLookTarget( m_hTalkTarget->GetAbsOrigin(), 1.0, 2.0 );
+ }
+ // look at player, but only if playing a "safe" idle animation
+ else if (m_hPlayer != NULL && (GetSequence() == 0 || IsInC5A1()) )
+ {
+ AddLookTarget( m_hPlayer->EyePosition(), 1.0, 3.0 );
+ }
+ else
+ {
+ // Just center the head forward.
+ Vector forward;
+ GetVectors( &forward, NULL, NULL );
+
+ AddLookTarget( GetAbsOrigin() + forward * 12.0f, 1.0, 1.0 );
+ SetBoneController( 0, 0 );
+ }
+ BaseClass::RunTask( pTask );
+ break;
+ }
+
+ SetBoneController( 0, 0 );
+ BaseClass::RunTask( pTask );
+}
+
+
+//=========================================================
+// Override all damage
+//=========================================================
+int CNPC_GMan::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ m_iHealth = m_iMaxHealth / 2; // always trigger the 50% damage aitrigger
+
+ if ( inputInfo.GetDamage() > 0 )
+ SetCondition( COND_LIGHT_DAMAGE );
+
+ if ( inputInfo.GetDamage() >= 20 )
+ SetCondition( COND_HEAVY_DAMAGE );
+
+ return TRUE;
+}
+
+
+void CNPC_GMan::TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType)
+{
+ g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal );
+// AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType );
+}
+
+int CNPC_GMan::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
+{
+ BaseClass::PlayScriptedSentence( pszSentence, delay, volume, soundlevel, bConcurrent, pListener );
+
+ m_flTalkTime = gpGlobals->curtime + delay;
+ m_hTalkTarget = pListener;
+
+ return 1;
+}
diff --git a/game/server/hl1/hl1_npc_hassassin.cpp b/game/server/hl1/hl1_npc_hassassin.cpp
new file mode 100644
index 0000000..fa978fe
--- /dev/null
+++ b/game/server/hl1/hl1_npc_hassassin.cpp
@@ -0,0 +1,951 @@
+//========= 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 "util.h"
+#include "hl1_ai_basenpc.h"
+#include "hl1_basegrenade.h"
+#include "movevars_shared.h"
+#include "ai_basenpc.h"
+
+
+ConVar sk_hassassin_health( "sk_hassassin_health", "50" );
+
+//=========================================================
+// monster-specific schedule types
+//=========================================================
+enum
+{
+ SCHED_ASSASSIN_EXPOSED = LAST_SHARED_SCHEDULE,// cover was blown.
+ SCHED_ASSASSIN_JUMP, // fly through the air
+ SCHED_ASSASSIN_JUMP_ATTACK, // fly through the air and shoot
+ SCHED_ASSASSIN_JUMP_LAND, // hit and run away
+ SCHED_ASSASSIN_FAIL,
+ SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1,
+ SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2,
+ SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND,
+ SCHED_ASSASSIN_HIDE,
+ SCHED_ASSASSIN_HUNT,
+};
+
+Activity ACT_ASSASSIN_FLY_UP;
+Activity ACT_ASSASSIN_FLY_ATTACK;
+Activity ACT_ASSASSIN_FLY_DOWN;
+
+//=========================================================
+// monster-specific tasks
+//=========================================================
+
+enum
+{
+ TASK_ASSASSIN_FALL_TO_GROUND = LAST_SHARED_TASK + 1, // falling and waiting to hit ground
+};
+
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define ASSASSIN_AE_SHOOT1 1
+#define ASSASSIN_AE_TOSS1 2
+#define ASSASSIN_AE_JUMP 3
+
+
+#define MEMORY_BADJUMP bits_MEMORY_CUSTOM1
+
+class CNPC_HAssassin : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_HAssassin, CHL1BaseNPC );
+
+public:
+ void Spawn( void );
+ void Precache( void );
+
+ int TranslateSchedule( int scheduleType );
+
+ void HandleAnimEvent( animevent_t *pEvent );
+ float MaxYawSpeed() { return 360.0f; }
+
+ void Shoot ( void );
+
+ int MeleeAttack1Conditions ( float flDot, float flDist );
+ int RangeAttack1Conditions ( float flDot, float flDist );
+ int RangeAttack2Conditions ( float flDot, float flDist );
+
+ int SelectSchedule ( void );
+
+ void RunTask ( const Task_t *pTask );
+ void StartTask ( const Task_t *pTask );
+
+ Class_T Classify ( void );
+
+ int GetSoundInterests( void );
+
+ void RunAI( void );
+
+ float m_flLastShot;
+ float m_flDiviation;
+
+ float m_flNextJump;
+ Vector m_vecJumpVelocity;
+
+ float m_flNextGrenadeCheck;
+ Vector m_vecTossVelocity;
+ bool m_fThrowGrenade;
+
+ int m_iTargetRanderamt;
+
+ int m_iFrustration;
+
+ int m_iAmmoType;
+
+public:
+ DECLARE_DATADESC();
+ DEFINE_CUSTOM_AI;
+
+};
+
+LINK_ENTITY_TO_CLASS( monster_human_assassin, CNPC_HAssassin );
+
+BEGIN_DATADESC( CNPC_HAssassin )
+ DEFINE_FIELD( m_flLastShot, FIELD_TIME ),
+ DEFINE_FIELD( m_flDiviation, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_flNextJump, FIELD_TIME ),
+ DEFINE_FIELD( m_vecJumpVelocity, FIELD_VECTOR ),
+
+ DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ),
+ DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_fThrowGrenade, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_iTargetRanderamt, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iFrustration, FIELD_INTEGER ),
+
+ //DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+
+END_DATADESC()
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_HAssassin::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/hassassin.mdl");
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+
+ SetNavType ( NAV_GROUND );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_RED;
+ ClearEffects();
+ m_iHealth = sk_hassassin_health.GetFloat();
+ m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ m_HackedGunPos = Vector( 0, 24, 48 );
+
+ m_iTargetRanderamt = 20;
+ SetRenderColor( 255, 255, 255, 20 );
+ m_nRenderMode = kRenderTransTexture;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND );
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_INNATE_MELEE_ATTACK1 );
+
+ NPCInit();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_HAssassin::Precache()
+{
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ PrecacheModel("models/hassassin.mdl");
+
+ UTIL_PrecacheOther( "npc_handgrenade" );
+
+ PrecacheScriptSound( "HAssassin.Shot" );
+ PrecacheScriptSound( "HAssassin.Beamsound" );
+ PrecacheScriptSound( "HAssassin.Footstep" );
+}
+
+int CNPC_HAssassin::GetSoundInterests( void )
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_PLAYER |
+ SOUND_DANGER;
+}
+
+Class_T CNPC_HAssassin::Classify ( void )
+{
+ return CLASS_HUMAN_MILITARY;
+}
+
+//=========================================================
+// CheckMeleeAttack1 - jump like crazy if the enemy gets too close.
+//=========================================================
+int CNPC_HAssassin::MeleeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( m_flNextJump < gpGlobals->curtime && ( flDist <= 128 || HasMemory( MEMORY_BADJUMP )) && GetEnemy() != NULL )
+ {
+ trace_t tr;
+
+ Vector vecMin = Vector( random->RandomFloat( 0, -64), random->RandomFloat( 0, -64 ), 0 );
+ Vector vecMax = Vector( random->RandomFloat( 0, 64), random->RandomFloat( 0, 64 ), 160 );
+
+ Vector vecDest = GetAbsOrigin() + Vector( random->RandomFloat( -64, 64), random->RandomFloat( -64, 64 ), 160 );
+
+ UTIL_TraceHull( GetAbsOrigin() + Vector( 0, 0, 36 ), GetAbsOrigin() + Vector( 0, 0, 36 ), vecMin, vecMax, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ //NDebugOverlay::Box( GetAbsOrigin() + Vector( 0, 0, 36 ), vecMin, vecMax, 0,0, 255, 0, 2.0 );
+
+ if ( tr.startsolid || tr.fraction < 1.0)
+ {
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+
+ float flGravity = GetCurrentGravity();
+
+ float time = sqrt( 160 / (0.5 * flGravity));
+ float speed = flGravity * time / 160;
+ m_vecJumpVelocity = ( vecDest - GetAbsOrigin() ) * speed;
+
+ return COND_CAN_MELEE_ATTACK1;
+ }
+
+ if ( flDist > 128 )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ return COND_NONE;
+}
+
+//=========================================================
+// CheckRangeAttack1 - drop a cap in their ass
+//
+//=========================================================
+int CNPC_HAssassin::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( !HasCondition( COND_ENEMY_OCCLUDED ) && flDist > 64 && flDist <= 2048 )
+ {
+ trace_t tr;
+
+ Vector vecSrc = GetAbsOrigin() + m_HackedGunPos;
+
+ // verify that a bullet fired from the gun will hit the enemy before the world.
+ UTIL_TraceLine( vecSrc, GetEnemy()->BodyTarget(vecSrc), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ if ( tr.fraction == 1.0 || tr.m_pEnt == GetEnemy() )
+ {
+ return COND_CAN_RANGE_ATTACK1;
+ }
+ }
+
+ return COND_NONE;
+}
+
+//=========================================================
+// CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close.
+//=========================================================
+int CNPC_HAssassin::RangeAttack2Conditions ( float flDot, float flDist )
+{
+ m_fThrowGrenade = false;
+ if ( !FBitSet ( GetEnemy()->GetFlags(), FL_ONGROUND ) )
+ {
+ // don't throw grenades at anything that isn't on the ground!
+ return COND_NONE;
+ }
+
+ // don't get grenade happy unless the player starts to piss you off
+ if ( m_iFrustration <= 2)
+ return COND_NONE;
+
+ if ( m_flNextGrenadeCheck < gpGlobals->curtime && !HasCondition( COND_ENEMY_OCCLUDED ) && flDist <= 512 )
+ {
+ Vector vTossPos;
+ QAngle vAngles;
+
+ GetAttachment( "grenadehand", vTossPos, vAngles );
+
+ Vector vecToss = VecCheckThrow( this, vTossPos, GetEnemy()->WorldSpaceCenter(), flDist, 0.5 ); // use dist as speed to get there in 1 second
+
+ if ( vecToss != vec3_origin )
+ {
+ m_vecTossVelocity = vecToss;
+
+ // throw a hand grenade
+ m_fThrowGrenade = TRUE;
+
+ return COND_CAN_RANGE_ATTACK2;
+ }
+ }
+
+ return COND_NONE;
+}
+
+//=========================================================
+// StartTask
+//=========================================================
+void CNPC_HAssassin::StartTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_RANGE_ATTACK2:
+ if (!m_fThrowGrenade)
+ {
+ TaskComplete( );
+ }
+ else
+ {
+ BaseClass::StartTask ( pTask );
+ }
+ break;
+ case TASK_ASSASSIN_FALL_TO_GROUND:
+ m_flWaitFinished = gpGlobals->curtime + 2.0f;
+ break;
+ default:
+ BaseClass::StartTask ( pTask );
+ break;
+ }
+}
+
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_HAssassin::RunTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_ASSASSIN_FALL_TO_GROUND:
+ GetMotor()->SetIdealYawAndUpdate( GetEnemyLKP() );
+
+ if ( IsSequenceFinished() )
+ {
+ if ( GetAbsVelocity().z > 0)
+ {
+ SetActivity( ACT_ASSASSIN_FLY_UP );
+ }
+ else if ( HasCondition ( COND_SEE_ENEMY ))
+ {
+ SetActivity( ACT_ASSASSIN_FLY_ATTACK );
+ SetCycle( 0 );
+ }
+ else
+ {
+ SetActivity( ACT_ASSASSIN_FLY_DOWN );
+ SetCycle( 0 );
+ }
+
+ ResetSequenceInfo( );
+ }
+
+ if ( GetFlags() & FL_ONGROUND)
+ {
+ TaskComplete( );
+ }
+ else if( gpGlobals->curtime > m_flWaitFinished || GetAbsVelocity().z == 0.0 )
+ {
+ // I've waited two seconds and haven't hit the ground. Try to force it.
+ trace_t trace;
+ UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1 ), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &trace );
+
+ if( trace.DidHitWorld() )
+ {
+ SetGroundEntity( trace.m_pEnt );
+ }
+ else
+ {
+ // Try again in a couple of seconds.
+ m_flWaitFinished = gpGlobals->curtime + 2.0f;
+ }
+ }
+ break;
+ default:
+ BaseClass::RunTask ( pTask );
+ break;
+ }
+}
+
+//=========================================================
+// GetSchedule - Decides which type of schedule best suits
+// the monster's current state and conditions. Then calls
+// monster's member function to get a pointer to a schedule
+// of the proper type.
+//=========================================================
+int CNPC_HAssassin::SelectSchedule ( void )
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_IDLE:
+ case NPC_STATE_ALERT:
+ {
+ if ( HasCondition ( COND_HEAR_DANGER ) || HasCondition ( COND_HEAR_COMBAT ) )
+ {
+ if ( HasCondition ( COND_HEAR_DANGER ) )
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+
+ else
+ return SCHED_INVESTIGATE_SOUND;
+ }
+ }
+ break;
+
+ case NPC_STATE_COMBAT:
+ {
+ // dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // call base class, all code to handle dead enemies is centralized there.
+ return BaseClass::SelectSchedule();
+ }
+
+ // flying?
+ if ( GetMoveType() == MOVETYPE_FLYGRAVITY )
+ {
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ //Msg( "landed\n" );
+ // just landed
+ SetMoveType( MOVETYPE_STEP );
+ return SCHED_ASSASSIN_JUMP_LAND;
+ }
+ else
+ {
+ //Msg("jump\n");
+ // jump or jump/shoot
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ return SCHED_ASSASSIN_JUMP;
+ else
+ return SCHED_ASSASSIN_JUMP_ATTACK;
+ }
+ }
+
+ if ( HasCondition ( COND_HEAR_DANGER ) )
+ {
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+ }
+
+ if ( HasCondition ( COND_LIGHT_DAMAGE ) )
+ {
+ m_iFrustration++;
+ }
+ if ( HasCondition ( COND_HEAVY_DAMAGE ) )
+ {
+ m_iFrustration++;
+ }
+
+ // jump player!
+ if ( HasCondition ( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ //Msg( "melee attack 1\n");
+ return SCHED_MELEE_ATTACK1;
+ }
+
+ // throw grenade
+ if ( HasCondition ( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ //Msg( "range attack 2\n");
+ return SCHED_RANGE_ATTACK2;
+ }
+
+ // spotted
+ if ( HasCondition ( COND_SEE_ENEMY ) && HasCondition ( COND_ENEMY_FACING_ME ) )
+ {
+ //Msg("exposed\n");
+ m_iFrustration++;
+ return SCHED_ASSASSIN_EXPOSED;
+ }
+
+ // can attack
+ if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ //Msg( "range attack 1\n" );
+ m_iFrustration = 0;
+ return SCHED_RANGE_ATTACK1;
+ }
+
+ if ( HasCondition ( COND_SEE_ENEMY ) )
+ {
+ //Msg( "face\n");
+ return SCHED_COMBAT_FACE;
+ }
+
+ // new enemy
+ if ( HasCondition ( COND_NEW_ENEMY ) )
+ {
+ //Msg( "take cover\n");
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+
+ // ALERT( at_console, "stand\n");
+ return SCHED_ALERT_STAND;
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//
+// Returns number of events handled, 0 if none.
+//=========================================================
+void CNPC_HAssassin::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case ASSASSIN_AE_SHOOT1:
+ Shoot( );
+ break;
+ case ASSASSIN_AE_TOSS1:
+ {
+ Vector vTossPos;
+ QAngle vAngles;
+
+ GetAttachment( "grenadehand", vTossPos, vAngles );
+
+ CHandGrenade *pGrenade = (CHandGrenade*)Create( "grenade_hand", vTossPos, vec3_angle );
+ if ( pGrenade )
+ {
+ pGrenade->ShootTimed( this, m_vecTossVelocity, 2.0 );
+ }
+
+ m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
+ m_fThrowGrenade = FALSE;
+ // !!!LATER - when in a group, only try to throw grenade if ordered.
+ }
+ break;
+ case ASSASSIN_AE_JUMP:
+ {
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+ SetGroundEntity( NULL );
+ SetAbsVelocity( m_vecJumpVelocity );
+ m_flNextJump = gpGlobals->curtime + 3.0;
+ }
+ return;
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+
+
+//=========================================================
+// Shoot
+//=========================================================
+void CNPC_HAssassin::Shoot ( void )
+{
+ Vector vForward, vRight, vUp;
+ Vector vecShootOrigin;
+ QAngle vAngles;
+
+ if ( GetEnemy() == NULL)
+ {
+ return;
+ }
+
+ GetAttachment( "guntip", vecShootOrigin, vAngles );
+
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin );
+
+ if (m_flLastShot + 2 < gpGlobals->curtime)
+ {
+ m_flDiviation = 0.10;
+ }
+ else
+ {
+ m_flDiviation -= 0.01;
+ if (m_flDiviation < 0.02)
+ m_flDiviation = 0.02;
+ }
+ m_flLastShot = gpGlobals->curtime;
+
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ Vector vecShellVelocity = vRight * random->RandomFloat(40,90) + vUp * random->RandomFloat(75,200) + vForward * random->RandomFloat(-40, 40);
+ EjectShell( GetAbsOrigin() + vUp * 32 + vForward * 12, vecShellVelocity, GetAbsAngles().y, 0 );
+ FireBullets( 1, vecShootOrigin, vecShootDir, Vector( m_flDiviation, m_flDiviation, m_flDiviation ), 2048, m_iAmmoType ); // shoot +-8 degrees
+
+ //NDebugOverlay::Line( vecShootOrigin, vecShootOrigin + vecShootDir * 2048, 255, 0, 0, true, 2.0 );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HAssassin.Shot" );
+
+ DoMuzzleFlash();
+
+ VectorAngles( vecShootDir, vAngles );
+ SetPoseParameter( "shoot", vecShootDir.x );
+
+ m_cAmmoLoaded--;
+}
+
+//=========================================================
+//=========================================================
+int CNPC_HAssassin::TranslateSchedule ( int scheduleType )
+{
+// Msg( "%d\n", m_iFrustration );
+ switch ( scheduleType )
+ {
+ case SCHED_TAKE_COVER_FROM_ENEMY:
+
+ if ( m_iHealth > 30 )
+ return SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1;
+ else
+ return SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2;
+
+ case SCHED_TAKE_COVER_FROM_BEST_SOUND:
+ return SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND;
+ case SCHED_FAIL:
+
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ return SCHED_ASSASSIN_FAIL;
+
+ break;
+ case SCHED_ALERT_STAND:
+
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ return SCHED_ASSASSIN_HIDE;
+
+ break;
+ //case SCHED_CHASE_ENEMY:
+ // return SCHED_ASSASSIN_HUNT;
+
+ case SCHED_MELEE_ATTACK1:
+
+ if ( GetFlags() & FL_ONGROUND)
+ {
+ if (m_flNextJump > gpGlobals->curtime)
+ {
+ // can't jump yet, go ahead and fail
+ return SCHED_ASSASSIN_FAIL;
+ }
+ else
+ {
+ return SCHED_ASSASSIN_JUMP;
+ }
+ }
+ else
+ {
+ return SCHED_ASSASSIN_JUMP_ATTACK;
+ }
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//=========================================================
+// RunAI
+//=========================================================
+void CNPC_HAssassin::RunAI( void )
+{
+ BaseClass::RunAI();
+
+ // always visible if moving
+ // always visible is not on hard
+ if (g_iSkillLevel != SKILL_HARD || GetEnemy() == NULL || m_lifeState == LIFE_DEAD || GetActivity() == ACT_RUN || GetActivity() == ACT_WALK || !(GetFlags() & FL_ONGROUND))
+ m_iTargetRanderamt = 255;
+ else
+ m_iTargetRanderamt = 20;
+
+ CPASAttenuationFilter filter( this );
+
+ if ( GetRenderColor().a > m_iTargetRanderamt)
+ {
+ if ( GetRenderColor().a == 255)
+ {
+ EmitSound( filter, entindex(), "HAssassin.Beamsound" );
+ }
+
+ SetRenderColorA( MAX( GetRenderColor().a - 50, m_iTargetRanderamt ) );
+ m_nRenderMode = kRenderTransTexture;
+ }
+ else if ( GetRenderColor().a < m_iTargetRanderamt)
+ {
+ SetRenderColorA ( MIN( GetRenderColor().a + 50, m_iTargetRanderamt ) );
+ if (GetRenderColor().a == 255)
+ m_nRenderMode = kRenderNormal;
+ }
+
+ if ( GetActivity() == ACT_RUN || GetActivity() == ACT_WALK)
+ {
+ static int iStep = 0;
+ iStep = ! iStep;
+ if (iStep)
+ {
+ EmitSound( filter, entindex(), "HAssassin.Footstep" );
+ }
+ }
+}
+
+AI_BEGIN_CUSTOM_NPC( monster_human_assassin, CNPC_HAssassin )
+
+ DECLARE_TASK( TASK_ASSASSIN_FALL_TO_GROUND )
+
+ DECLARE_ACTIVITY( ACT_ASSASSIN_FLY_UP )
+ DECLARE_ACTIVITY( ACT_ASSASSIN_FLY_ATTACK )
+ DECLARE_ACTIVITY( ACT_ASSASSIN_FLY_DOWN )
+
+ //=========================================================
+ // AI Schedules Specific to this monster
+ //=========================================================
+
+ //=========================================================
+ // Enemy exposed assasin's cover
+ //=========================================================
+
+ //=========================================================
+ // > SCHED_ASSASSIN_EXPOSED
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_EXPOSED,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
+ " "
+ " Interrupts"
+ " COND_CAN_MELEE_ATTACK1"
+
+ )
+
+ //=========================================================
+ // > SCHED_ASSASSIN_JUMP
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_JUMP,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_HOP"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP_ATTACK"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_ASSASSIN_JUMP_ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_JUMP_ATTACK,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_JUMP_LAND"
+ " TASK_ASSASSIN_FALL_TO_GROUND 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_ASSASSIN_JUMP_LAND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_JUMP_LAND,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_EXPOSED"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_REMEMBER MEMORY:CUSTOM1"
+ " TASK_FIND_NODE_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_FORGET MEMORY:CUSTOM1"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_FACE_ENEMY 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1"
+
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // Fail Schedule
+ //=========================================================
+
+ //=========================================================
+ // > SCHED_ASSASSIN_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 2"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ )
+
+
+
+
+ //=========================================================
+ // > SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT 0.2"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_FACE_ENEMY 0"
+ " "
+ " Interrupts"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+
+ )
+
+ //=========================================================
+ // > SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT 0.2"
+ " TASK_FACE_ENEMY 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_FACE_ENEMY 0"
+ " "
+ " Interrupts"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+
+ )
+
+
+
+ //=========================================================
+ // hide from the loudest sound source
+ //=========================================================
+
+ //=========================================================
+ // > SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_TAKE_COVER_FROM_BEST_SOUND,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MELEE_ATTACK1"
+ " TASK_STOP_MOVING 0"
+ " TASK_FIND_COVER_FROM_BEST_SOUND 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_TURN_LEFT 179"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+
+ )
+
+ //=========================================================
+ // > SCHED_ASSASSIN_HIDE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_HIDE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2.0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
+
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_ENEMY"
+ " COND_SEE_FEAR"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_PROVOKED"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_ASSASSIN_HUNT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_ASSASSIN_HUNT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASSASSIN_TAKE_COVER_FROM_ENEMY2"
+ " TASK_GET_PATH_TO_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_HEAR_DANGER"
+ )
+
+AI_END_CUSTOM_NPC()
diff --git a/game/server/hl1/hl1_npc_headcrab.cpp b/game/server/hl1/hl1_npc_headcrab.cpp
new file mode 100644
index 0000000..ed41a93
--- /dev/null
+++ b/game/server/hl1/hl1_npc_headcrab.cpp
@@ -0,0 +1,698 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Implements the headcrab, a tiny, jumpy alien parasite.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "game.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_route.h"
+#include "ai_motor.h"
+#include "npcevent.h"
+#include "hl1_npc_headcrab.h"
+#include "gib.h"
+//#include "AI_Interactions.h"
+#include "ndebugoverlay.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "movevars_shared.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+
+extern void ClearMultiDamage(void);
+extern void ApplyMultiDamage( void );
+
+ConVar sk_headcrab_health( "sk_headcrab_health","20");
+ConVar sk_headcrab_dmg_bite( "sk_headcrab_dmg_bite","10");
+
+#define CRAB_ATTN_IDLE (float)1.5
+#define HEADCRAB_GUTS_GIB_COUNT 1
+#define HEADCRAB_LEGS_GIB_COUNT 3
+#define HEADCRAB_ALL_GIB_COUNT 5
+
+#define HEADCRAB_MAX_JUMP_DIST 256
+
+#define HEADCRAB_RUNMODE_ACCELERATE 1
+#define HEADCRAB_RUNMODE_IDLE 2
+#define HEADCRAB_RUNMODE_DECELERATE 3
+#define HEADCRAB_RUNMODE_FULLSPEED 4
+#define HEADCRAB_RUNMODE_PAUSE 5
+
+#define HEADCRAB_RUN_MINSPEED 0.5
+#define HEADCRAB_RUN_MAXSPEED 1.0
+
+#define HC_AE_JUMPATTACK ( 2 )
+
+BEGIN_DATADESC( CNPC_Headcrab )
+ // m_nGibCount - don't save
+
+ // Function Pointers
+ DEFINE_ENTITYFUNC( LeapTouch ),
+ DEFINE_FIELD( m_vecJumpVel, FIELD_VECTOR ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( monster_headcrab, CNPC_Headcrab );
+
+
+enum
+{
+ SCHED_HEADCRAB_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE,
+ SCHED_FAST_HEADCRAB_RANGE_ATTACK1,
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::Spawn( void )
+{
+ Precache();
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ SetModel( "models/headcrab.mdl" );
+ m_iHealth = sk_headcrab_health.GetFloat();
+
+ SetHullType(HULL_TINY);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ SetViewOffset( Vector(6, 0, 11) ); // Position of the eyes relative to NPC's origin.
+
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ m_flFieldOfView = 0.5;
+ m_NPCState = NPC_STATE_NONE;
+ m_nGibCount = HEADCRAB_ALL_GIB_COUNT;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
+
+ NPCInit();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::Precache( void )
+{
+ PrecacheModel( "models/headcrab.mdl" );
+// PrecacheModel( "models/hc_squashed01.mdl" );
+// PrecacheModel( "models/gibs/hc_gibs.mdl" );
+
+ PrecacheScriptSound( "Headcrab.Bite" );
+ PrecacheScriptSound( "Headcrab.Attack" );
+ PrecacheScriptSound( "Headcrab.Idle" );
+ PrecacheScriptSound( "Headcrab.Die" );
+ PrecacheScriptSound( "Headcrab.Alert" );
+ PrecacheScriptSound( "Headcrab.Pain" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::IdleSound()
+{
+ HeadCrabSound( "Headcrab.Idle" );
+}
+
+
+void CNPC_Headcrab::AlertSound()
+{
+ HeadCrabSound( "Headcrab.Alert" );
+}
+
+
+void CNPC_Headcrab::PainSound( const CTakeDamageInfo &info )
+{
+ HeadCrabSound( "Headcrab.Pain" );
+}
+
+
+void CNPC_Headcrab::DeathSound( const CTakeDamageInfo &info )
+{
+ HeadCrabSound( "Headcrab.Die" );
+}
+
+void CNPC_Headcrab::HeadCrabSound( const char *pchSound )
+{
+ CPASAttenuationFilter filter( this, ATTN_IDLE );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( pchSound, params, NULL ) )
+ {
+ EmitSound_t ep( params );
+
+ ep.m_flVolume = GetSoundVolume();
+ ep.m_nPitch = GetVoicePitch();
+
+ EmitSound( filter, entindex(), ep );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pTask -
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::StartTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_RANGE_ATTACK1:
+ {
+ SetIdealActivity( ACT_RANGE_ATTACK1 );
+ SetTouch( &CNPC_Headcrab::LeapTouch );
+ break;
+ }
+
+ default:
+ {
+ BaseClass::StartTask( pTask );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTask -
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_RANGE_ATTACK1:
+ case TASK_RANGE_ATTACK2:
+ {
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete();
+ SetTouch( NULL );
+ SetIdealActivity( ACT_IDLE );
+ }
+ break;
+ }
+
+ default:
+ {
+ CAI_BaseNPC::RunTask( pTask );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output :
+//-----------------------------------------------------------------------------
+int CNPC_Headcrab::SelectSchedule( void )
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_ALERT:
+ {
+ if (HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ))
+ {
+ if ( fabs( GetMotor()->DeltaIdealYaw() ) < ( 1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction
+ {
+ return SCHED_TAKE_COVER_FROM_ORIGIN;
+ }
+ else if ( SelectWeightedSequence( ACT_SMALL_FLINCH ) != -1 )
+ {
+ return SCHED_SMALL_FLINCH;
+ }
+ }
+ else if (HasCondition( COND_HEAR_DANGER ) ||
+ HasCondition( COND_HEAR_PLAYER ) ||
+ HasCondition( COND_HEAR_WORLD ) ||
+ HasCondition( COND_HEAR_COMBAT ))
+ {
+ return SCHED_ALERT_FACE_BESTSOUND;
+ }
+ else
+ {
+ return SCHED_PATROL_WALK;
+ }
+ break;
+ }
+ }
+
+ // no special cases here, call the base class
+ return BaseClass::SelectSchedule();
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Headcrab::Touch( CBaseEntity *pOther )
+{
+ // If someone has smacked me into a wall then gib!
+/* if (m_NPCState == NPC_STATE_DEAD)
+ {
+ if (GetAbsVelocity().Length() > 250)
+ {
+ trace_t tr;
+ Vector vecDir = GetAbsVelocity();
+ VectorNormalize(vecDir);
+ UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vecDir * 100,
+ MASK_SOLID_BRUSHONLY, pev, COLLISION_GROUP_NONE, &tr);
+ float dotPr = DotProduct(vecDir,tr.plane.normal);
+ if ((tr.fraction != 1.0) &&
+ (dotPr < -0.8) )
+ {
+ Event_Gibbed();
+ // Throw headcrab guts
+ CGib::SpawnSpecificGibs( this, HEADCRAB_GUTS_GIB_COUNT, 300, 400, "models/gibs/hc_gibs.mdl");
+ }
+
+ }
+ }*/
+ BaseClass::Touch(pOther);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pevInflictor -
+// pevAttacker -
+// flDamage -
+// bitsDamageType -
+// Output :
+//-----------------------------------------------------------------------------
+int CNPC_Headcrab::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ //
+ // Don't take any acid damage.
+ //
+ if ( info.GetDamageType() & DMG_ACID )
+ {
+ return 0;
+ }
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+float CNPC_Headcrab::GetDamageAmount( void )
+{
+ return sk_headcrab_dmg_bite.GetFloat();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : Type -
+// Output : CAI_Schedule *
+//-----------------------------------------------------------------------------
+int CNPC_Headcrab::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_RANGE_ATTACK1:
+ return SCHED_HEADCRAB_RANGE_ATTACK1;
+
+ case SCHED_FAIL_TAKE_COVER:
+ return SCHED_ALERT_FACE;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ //
+ // Make the crab coo a little bit in combat state.
+ //
+ if (( m_NPCState == NPC_STATE_COMBAT ) && ( random->RandomFloat( 0, 5 ) < 0.1 ))
+ {
+ IdleSound();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: For innate melee attack
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+int CNPC_Headcrab::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( gpGlobals->curtime < m_flNextAttack )
+ {
+ return( 0 );
+ }
+
+ if ( !(GetFlags() & FL_ONGROUND) )
+ {
+ return( 0 );
+ }
+
+ if ( flDist > 256 )
+ {
+ return( COND_TOO_FAR_TO_ATTACK );
+ }
+ else if ( flDot < 0.65 )
+ {
+ return( COND_NOT_FACING_ATTACK );
+ }
+
+ return( COND_CAN_RANGE_ATTACK1 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates this monster's place in the relationship table.
+// Output :
+//-----------------------------------------------------------------------------
+Class_T CNPC_Headcrab::Classify( void )
+{
+ return CLASS_ALIEN_PREY;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the real center of the monster. The bounding box is much larger
+// than the actual creature so this is needed for targetting.
+// Output : Vector
+//-----------------------------------------------------------------------------
+Vector CNPC_Headcrab::Center( void )
+{
+ return Vector( GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z + 6 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &posSrc -
+// Output : Vector
+//-----------------------------------------------------------------------------
+Vector CNPC_Headcrab::BodyTarget( const Vector &posSrc, bool bNoisy )
+{
+ return( Center() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+float CNPC_Headcrab::MaxYawSpeed ( void )
+{
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ return 30;
+ break;
+
+ case ACT_RUN:
+ case ACT_WALK:
+ return 20;
+ break;
+
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 15;
+ break;
+
+ case ACT_RANGE_ATTACK1:
+ return 30;
+ break;
+
+ default:
+ return 30;
+ break;
+ }
+
+ return BaseClass::MaxYawSpeed();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: LeapTouch - this is the headcrab's touch function when it is in the air.
+// Input : *pOther -
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::LeapTouch( CBaseEntity *pOther )
+{
+ if ( pOther->Classify() == Classify() )
+ {
+ return;
+ }
+
+ // Don't hit if back on ground
+ if ( !(GetFlags() & FL_ONGROUND) && ( pOther->IsNPC() || pOther->IsPlayer() ) )
+ {
+ BiteSound();
+ TouchDamage( pOther );
+ }
+
+ SetTouch( NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Make the sound of this headcrab chomping a target.
+// Input :
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::BiteSound( void )
+{
+ HeadCrabSound( "Headcrab.Bite" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Deal the damage from the headcrab's touch attack.
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::TouchDamage( CBaseEntity *pOther )
+{
+ CTakeDamageInfo info( this, this, GetDamageAmount(), DMG_SLASH );
+ CalculateMeleeDamageForce( &info, GetAbsVelocity(), GetAbsOrigin() );
+ pOther->TakeDamage( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Catches the monster-specific messages that occur when tagged
+// animation frames are played.
+// Input : *pEvent -
+//-----------------------------------------------------------------------------
+void CNPC_Headcrab::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch ( pEvent->event )
+ {
+ case HC_AE_JUMPATTACK:
+ {
+ SetGroundEntity( NULL );
+
+ //
+ // Take him off ground so engine doesn't instantly reset FL_ONGROUND.
+ //
+ UTIL_SetOrigin( this, GetAbsOrigin() + Vector( 0 , 0 , 1 ));
+
+ Vector vecJumpDir;
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( pEnemy )
+ {
+ Vector vecEnemyEyePos = pEnemy->EyePosition();
+
+ float gravity = GetCurrentGravity();
+ if ( gravity <= 1 )
+ {
+ gravity = 1;
+ }
+
+ //
+ // How fast does the headcrab need to travel to reach my enemy's eyes given gravity?
+ //
+ float height = ( vecEnemyEyePos.z - GetAbsOrigin().z );
+ if ( height < 16 )
+ {
+ height = 16;
+ }
+ else if ( height > 120 )
+ {
+ height = 120;
+ }
+ float speed = sqrt( 2 * gravity * height );
+ float time = speed / gravity;
+
+ //
+ // Scale the sideways velocity to get there at the right time
+ //
+ vecJumpDir = vecEnemyEyePos - GetAbsOrigin();
+ vecJumpDir = vecJumpDir / time;
+
+ //
+ // Speed to offset gravity at the desired height.
+ //
+ vecJumpDir.z = speed;
+
+ //
+ // Don't jump too far/fast.
+ //
+ float distance = vecJumpDir.Length();
+ if ( distance > 650 )
+ {
+ vecJumpDir = vecJumpDir * ( 650.0 / distance );
+ }
+ }
+ else
+ {
+ //
+ // Jump hop, don't care where.
+ //
+ Vector forward, up;
+ AngleVectors( GetAbsAngles(), &forward, NULL, &up );
+ vecJumpDir = Vector( forward.x, forward.y, up.z ) * 350;
+ }
+
+ int iSound = random->RandomInt( 0 , 2 );
+ if ( iSound != 0 )
+ {
+ AttackSound();
+ }
+
+ SetAbsVelocity( vecJumpDir );
+ m_flNextAttack = gpGlobals->curtime + 2;
+ break;
+ }
+
+ default:
+ {
+ CAI_BaseNPC::HandleAnimEvent( pEvent );
+ break;
+ }
+ }
+}
+
+void CNPC_Headcrab::AttackSound( void )
+{
+ HeadCrabSound( "Headcrab.Attack" );
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( monster_headcrab, CNPC_Headcrab )
+
+ //=========================================================
+ // > SCHED_HEADCRAB_RANGE_ATTACK1
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HEADCRAB_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_IDEAL 0"
+ " TASK_WAIT_RANDOM 0.5"
+ " "
+ " Interrupts"
+ " COND_ENEMY_OCCLUDED"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+ //=========================================================
+ // > SCHED_FAST_HEADCRAB_RANGE_ATTACK1
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_FAST_HEADCRAB_RANGE_ATTACK1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " "
+ " Interrupts"
+ " COND_ENEMY_OCCLUDED"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+AI_END_CUSTOM_NPC()
+
+
+class CNPC_BabyCrab : public CNPC_Headcrab
+{
+ DECLARE_CLASS( CNPC_BabyCrab, CNPC_Headcrab );
+public:
+ void Spawn( void );
+ void Precache( void );
+
+ unsigned int PhysicsSolidMaskForEntity( void ) const;
+
+ int RangeAttack1Conditions ( float flDot, float flDist );
+ float MaxYawSpeed( void ){ return 120.0f; }
+ float GetDamageAmount( void );
+
+ virtual int GetVoicePitch( void ) { return PITCH_NORM + random->RandomInt( 40,50 ); }
+ virtual float GetSoundVolume( void ) { return 0.8; }
+};
+LINK_ENTITY_TO_CLASS( monster_babycrab, CNPC_BabyCrab );
+
+unsigned int CNPC_BabyCrab::PhysicsSolidMaskForEntity( void ) const
+{
+ unsigned int iMask = BaseClass::PhysicsSolidMaskForEntity();
+
+ iMask &= ~CONTENTS_MONSTERCLIP;
+
+ return iMask;
+}
+
+void CNPC_BabyCrab::Spawn( void )
+{
+ CNPC_Headcrab::Spawn();
+ SetModel( "models/baby_headcrab.mdl" );
+ m_nRenderMode = kRenderTransTexture;
+
+ SetRenderColor( 255, 255, 255, 192 );
+
+ UTIL_SetSize(this, Vector(-12, -12, 0), Vector(12, 12, 24));
+
+ m_iHealth = sk_headcrab_health.GetFloat() * 0.25; // less health than full grown
+}
+
+void CNPC_BabyCrab::Precache( void )
+{
+ PrecacheModel( "models/baby_headcrab.mdl" );
+ CNPC_Headcrab::Precache();
+}
+
+int CNPC_BabyCrab::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ if ( GetGroundEntity() && ( GetGroundEntity()->GetFlags() & ( FL_CLIENT | FL_NPC ) ) )
+ return COND_CAN_RANGE_ATTACK1;
+
+ // A little less accurate, but jump from closer
+ if ( flDist <= 180 && flDot >= 0.55 )
+ return COND_CAN_RANGE_ATTACK1;
+ }
+
+ return COND_NONE;
+}
+
+float CNPC_BabyCrab::GetDamageAmount( void )
+{
+ return sk_headcrab_dmg_bite.GetFloat() * 0.3;
+}
diff --git a/game/server/hl1/hl1_npc_headcrab.h b/game/server/hl1/hl1_npc_headcrab.h
new file mode 100644
index 0000000..c047415
--- /dev/null
+++ b/game/server/hl1/hl1_npc_headcrab.h
@@ -0,0 +1,63 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_HEADCRAB_H
+#define NPC_HEADCRAB_H
+#pragma once
+
+
+#include "hl1_ai_basenpc.h"
+
+class CNPC_Headcrab : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Headcrab, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+
+ void RunTask ( const Task_t *pTask );
+ void StartTask ( const Task_t *pTask );
+ void SetYawSpeed ( void );
+ Vector Center( void );
+ Vector BodyTarget( const Vector &posSrc, bool bNoisy = true );
+
+ float MaxYawSpeed( void );
+ Class_T Classify( void );
+
+ void LeapTouch ( CBaseEntity *pOther );
+ void BiteSound( void );
+ void AttackSound( void );
+ void TouchDamage( CBaseEntity *pOther );
+ void HandleAnimEvent( animevent_t *pEvent );
+ int SelectSchedule( void );
+ void Touch( CBaseEntity *pOther );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+ int TranslateSchedule( int scheduleType );
+ void PrescheduleThink( void );
+ int RangeAttack1Conditions ( float flDot, float flDist );
+ float GetDamageAmount( void );
+ virtual void PainSound( const CTakeDamageInfo &info );
+ virtual void DeathSound( const CTakeDamageInfo &info );
+ virtual void IdleSound();
+ virtual void AlertSound();
+
+ virtual int GetVoicePitch( void ) { return 100; }
+ virtual float GetSoundVolume( void ) { return 1.0; }
+
+ int m_nGibCount;
+
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+
+protected:
+ void HeadCrabSound( const char *pchSound );
+
+ Vector m_vecJumpVel;
+};
+
+#endif //NPC_HEADCRAB_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_hgrunt.cpp b/game/server/hl1/hl1_npc_hgrunt.cpp
new file mode 100644
index 0000000..375ddf7
--- /dev/null
+++ b/game/server/hl1/hl1_npc_hgrunt.cpp
@@ -0,0 +1,2645 @@
+//========= 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 "beam_shared.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 "hl1_npc_hgrunt.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "animation.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "basecombatweapon.h"
+#include "hl1_basegrenade.h"
+#include "ai_interactions.h"
+#include "scripted.h"
+#include "hl1_basegrenade.h"
+#include "hl1_grenade_mp5.h"
+
+ConVar sk_hgrunt_health( "sk_hgrunt_health","0");
+ConVar sk_hgrunt_kick ( "sk_hgrunt_kick", "0" );
+ConVar sk_hgrunt_pellets ( "sk_hgrunt_pellets", "0" );
+ConVar sk_hgrunt_gspeed ( "sk_hgrunt_gspeed", "0" );
+
+extern ConVar sk_plr_dmg_grenade;
+extern ConVar sk_plr_dmg_mp5_grenade;
+
+#define SF_GRUNT_LEADER ( 1 << 5 )
+
+int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers.
+int g_iSquadIndex = 0;
+
+#define HGRUNT_GUN_SPREAD 0.08716f
+
+//=========================================================
+// monster-specific DEFINE's
+//=========================================================
+#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x!
+#define GRUNT_VOL 0.35 // volume of grunt sounds
+#define GRUNT_SNDLVL SNDLVL_NORM // soundlevel of grunt sentences
+#define HGRUNT_LIMP_HEALTH 20
+#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot.
+#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there?
+#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill
+#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences
+
+#define HGRUNT_9MMAR ( 1 << 0)
+#define HGRUNT_HANDGRENADE ( 1 << 1)
+#define HGRUNT_GRENADELAUNCHER ( 1 << 2)
+#define HGRUNT_SHOTGUN ( 1 << 3)
+
+#define HEAD_GROUP 1
+#define HEAD_GRUNT 0
+#define HEAD_COMMANDER 1
+#define HEAD_SHOTGUN 2
+#define HEAD_M203 3
+#define GUN_GROUP 2
+#define GUN_MP5 0
+#define GUN_SHOTGUN 1
+#define GUN_NONE 2
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define HGRUNT_AE_RELOAD ( 2 )
+#define HGRUNT_AE_KICK ( 3 )
+#define HGRUNT_AE_BURST1 ( 4 )
+#define HGRUNT_AE_BURST2 ( 5 )
+#define HGRUNT_AE_BURST3 ( 6 )
+#define HGRUNT_AE_GREN_TOSS ( 7 )
+#define HGRUNT_AE_GREN_LAUNCH ( 8 )
+#define HGRUNT_AE_GREN_DROP ( 9 )
+#define HGRUNT_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad.
+#define HGRUNT_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5.
+
+
+const char *CNPC_HGrunt::pGruntSentences[] =
+{
+ "HG_GREN", // grenade scared grunt
+ "HG_ALERT", // sees player
+ "HG_MONSTER", // sees monster
+ "HG_COVER", // running to cover
+ "HG_THROW", // about to throw grenade
+ "HG_CHARGE", // running out to get the enemy
+ "HG_TAUNT", // say rude things
+};
+
+enum HGRUNT_SENTENCE_TYPES
+{
+ HGRUNT_SENT_NONE = -1,
+ HGRUNT_SENT_GREN = 0,
+ HGRUNT_SENT_ALERT,
+ HGRUNT_SENT_MONSTER,
+ HGRUNT_SENT_COVER,
+ HGRUNT_SENT_THROW,
+ HGRUNT_SENT_CHARGE,
+ HGRUNT_SENT_TAUNT,
+} ;
+
+LINK_ENTITY_TO_CLASS( monster_human_grunt, CNPC_HGrunt );
+
+//=========================================================
+// monster-specific schedule types
+//=========================================================
+enum
+{
+ SCHED_GRUNT_FAIL = LAST_SHARED_SCHEDULE,
+ SCHED_GRUNT_COMBAT_FAIL,
+ SCHED_GRUNT_VICTORY_DANCE,
+ SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,
+ SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE_RETRY,
+ SCHED_GRUNT_FOUND_ENEMY,
+ SCHED_GRUNT_COMBAT_FACE,
+ SCHED_GRUNT_SIGNAL_SUPPRESS,
+ SCHED_GRUNT_SUPPRESS,
+ SCHED_GRUNT_WAIT_IN_COVER,
+ SCHED_GRUNT_TAKE_COVER,
+ SCHED_GRUNT_GRENADE_COVER,
+ SCHED_GRUNT_TOSS_GRENADE_COVER,
+ SCHED_GRUNT_HIDE_RELOAD,
+ SCHED_GRUNT_SWEEP,
+ SCHED_GRUNT_RANGE_ATTACK1A,
+ SCHED_GRUNT_RANGE_ATTACK1B,
+ SCHED_GRUNT_RANGE_ATTACK2,
+ SCHED_GRUNT_REPEL,
+ SCHED_GRUNT_REPEL_ATTACK,
+ SCHED_GRUNT_REPEL_LAND,
+ SCHED_GRUNT_TAKE_COVER_FAILED,
+ SCHED_GRUNT_RELOAD,
+ SCHED_GRUNT_TAKE_COVER_FROM_ENEMY,
+ SCHED_GRUNT_BARNACLE_HIT,
+ SCHED_GRUNT_BARNACLE_PULL,
+ SCHED_GRUNT_BARNACLE_CHOMP,
+ SCHED_GRUNT_BARNACLE_CHEW,
+};
+
+//=========================================================
+// monster-specific tasks
+//=========================================================
+enum
+{
+ TASK_GRUNT_FACE_TOSS_DIR = LAST_SHARED_TASK + 1,
+ TASK_GRUNT_SPEAK_SENTENCE,
+ TASK_GRUNT_CHECK_FIRE,
+};
+
+
+//=========================================================
+// monster-specific conditions
+//=========================================================
+enum
+{
+ COND_GRUNT_NOFIRE = LAST_SHARED_CONDITION + 1,
+};
+
+// -----------------------------------------------
+// > Squad slots
+// -----------------------------------------------
+enum SquadSlot_T
+{
+ SQUAD_SLOT_GRENADE1 = LAST_SHARED_SQUADSLOT,
+ SQUAD_SLOT_GRENADE2,
+ SQUAD_SLOT_ENGAGE1,
+ SQUAD_SLOT_ENGAGE2,
+};
+
+
+int ACT_GRUNT_LAUNCH_GRENADE;
+int ACT_GRUNT_TOSS_GRENADE;
+int ACT_GRUNT_MP5_STANDING;
+int ACT_GRUNT_MP5_CROUCHING;
+int ACT_GRUNT_SHOTGUN_STANDING;
+int ACT_GRUNT_SHOTGUN_CROUCHING;
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_HGrunt )
+ DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextPainTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flCheckAttackTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_iLastGrenadeCondition, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fStanding, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fFirstEncounter, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_iClipSize, FIELD_INTEGER ),
+ DEFINE_FIELD( m_voicePitch, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iSentence, FIELD_INTEGER ),
+ DEFINE_KEYFIELD( m_iWeapons, FIELD_INTEGER, "weapons" ),
+ DEFINE_KEYFIELD( m_SquadName, FIELD_STRING, "netname" ),
+
+ DEFINE_FIELD( m_bInBarnacleMouth, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flLastEnemySightTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTalkWaitTime, FIELD_TIME ),
+ //DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+
+END_DATADESC()
+
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_HGrunt::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/hgrunt.mdl" );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_RED;
+ ClearEffects();
+ m_iHealth = sk_hgrunt_health.GetFloat();
+ m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1;
+ m_flNextPainTime = gpGlobals->curtime;
+ m_iSentence = HGRUNT_SENT_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd ( bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP | bits_CAP_MOVE_GROUND );
+
+ CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK1 );
+
+ // Innate range attack for grenade
+ CapabilitiesAdd(bits_CAP_INNATE_RANGE_ATTACK2 );
+ // Innate range attack for kicking
+ CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1 );
+
+ m_fFirstEncounter = true;// this is true when the grunt spawns, because he hasn't encountered an enemy yet.
+
+ m_HackedGunPos = Vector ( 0, 0, 55 );
+
+ if ( m_iWeapons == 0)
+ {
+ // initialize to original values
+ m_iWeapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE;
+ // pev->weapons = HGRUNT_SHOTGUN;
+ // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER;
+ }
+
+ if (FBitSet( m_iWeapons, HGRUNT_SHOTGUN ))
+ {
+ SetBodygroup( GUN_GROUP, GUN_SHOTGUN );
+ m_iClipSize = 8;
+ }
+ else
+ {
+ m_iClipSize = GRUNT_CLIP_SIZE;
+ }
+ m_cAmmoLoaded = m_iClipSize;
+
+ if ( random->RandomInt( 0, 99 ) < 80)
+ m_nSkin = 0; // light skin
+ else
+ m_nSkin = 1; // dark skin
+
+ if (FBitSet( m_iWeapons, HGRUNT_SHOTGUN ))
+ {
+ SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN);
+ }
+ else if (FBitSet( m_iWeapons, HGRUNT_GRENADELAUNCHER ))
+ {
+ SetBodygroup( HEAD_GROUP, HEAD_M203 );
+ m_nSkin = 1; // alway dark skin
+ }
+
+ m_flTalkWaitTime = 0;
+
+
+ //HACK
+ g_iSquadIndex = 0;
+
+ BaseClass::Spawn();
+
+ NPCInit();
+}
+
+int CNPC_HGrunt::IRelationPriority( CBaseEntity *pTarget )
+{
+ //I hate alien grunts more than anything.
+ if ( pTarget->Classify() == CLASS_ALIEN_MILITARY )
+ {
+ if ( FClassnameIs( pTarget, "monster_alien_grunt" ) )
+ {
+ return ( BaseClass::IRelationPriority ( pTarget ) + 1 );
+ }
+ }
+
+ return BaseClass::IRelationPriority( pTarget );
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_HGrunt::Precache()
+{
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ PrecacheModel("models/hgrunt.mdl");
+
+ // get voice pitch
+ if ( random->RandomInt(0,1))
+ m_voicePitch = 109 + random->RandomInt(0,7);
+ else
+ m_voicePitch = 100;
+
+ PrecacheScriptSound( "HGrunt.Reload" );
+ PrecacheScriptSound( "HGrunt.GrenadeLaunch" );
+ PrecacheScriptSound( "HGrunt.9MM" );
+ PrecacheScriptSound( "HGrunt.Shotgun" );
+ PrecacheScriptSound( "HGrunt.Pain" );
+ PrecacheScriptSound( "HGrunt.Die" );
+
+ BaseClass::Precache();
+
+ UTIL_PrecacheOther( "grenade_hand" );
+ UTIL_PrecacheOther( "grenade_mp5" );
+}
+
+//=========================================================
+// someone else is talking - don't speak
+//=========================================================
+bool CNPC_HGrunt::FOkToSpeak( void )
+{
+// if someone else is talking, don't speak
+ if ( gpGlobals->curtime <= m_flTalkWaitTime )
+ return FALSE;
+
+ if ( m_spawnflags & SF_NPC_GAG )
+ {
+ if ( m_NPCState != NPC_STATE_COMBAT )
+ {
+ // no talking outside of combat if gagged.
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+
+//=========================================================
+// Speak Sentence - say your cued up sentence.
+//
+// Some grunt sentences (take cover and charge) rely on actually
+// being able to execute the intended action. It's really lame
+// when a grunt says 'COVER ME' and then doesn't move. The problem
+// is that the sentences were played when the decision to TRY
+// to move to cover was made. Now the sentence is played after
+// we know for sure that there is a valid path. The schedule
+// may still fail but in most cases, well after the grunt has
+// started moving.
+//=========================================================
+void CNPC_HGrunt::SpeakSentence( void )
+{
+ if ( m_iSentence == HGRUNT_SENT_NONE )
+ {
+ // no sentence cued up.
+ return;
+ }
+
+ if ( FOkToSpeak() )
+ {
+ SENTENCEG_PlayRndSz( edict(), pGruntSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+}
+
+//=========================================================
+//=========================================================
+void CNPC_HGrunt::JustSpoke( void )
+{
+ m_flTalkWaitTime = gpGlobals->curtime + random->RandomFloat( 1.5f, 2.0f );
+ m_iSentence = HGRUNT_SENT_NONE;
+}
+
+//=========================================================
+// PrescheduleThink - this function runs after conditions
+// are collected and before scheduling code is run.
+//=========================================================
+void CNPC_HGrunt::PrescheduleThink ( void )
+{
+ BaseClass::PrescheduleThink();
+
+ if ( m_pSquad && GetEnemy() != NULL )
+ {
+ if ( m_pSquad->GetLeader() == NULL )
+ return;
+
+ CNPC_HGrunt *pSquadLeader = (CNPC_HGrunt*)m_pSquad->GetLeader()->MyNPCPointer();
+
+ if ( pSquadLeader == NULL )
+ return; //Paranoid, so making sure it's ok.
+
+ if ( HasCondition ( COND_SEE_ENEMY ) )
+ {
+ // update the squad's last enemy sighting time.
+ pSquadLeader->m_flLastEnemySightTime = gpGlobals->curtime;
+ }
+ else
+ {
+ if ( gpGlobals->curtime - pSquadLeader->m_flLastEnemySightTime > 5 )
+ {
+ // been a while since we've seen the enemy
+ pSquadLeader->GetEnemies()->MarkAsEluded( GetEnemy() );
+ }
+ }
+ }
+}
+
+Class_T CNPC_HGrunt::Classify ( void )
+{
+ return CLASS_HUMAN_MILITARY;
+}
+
+//=========================================================
+//
+// SquadRecruit(), get some monsters of my classification and
+// link them as a group. returns the group size
+//
+//=========================================================
+int CNPC_HGrunt::SquadRecruit( int searchRadius, int maxMembers )
+{
+ int squadCount;
+ int iMyClass = Classify();// cache this monster's class
+
+ if ( maxMembers < 2 )
+ return 0;
+
+ // I am my own leader
+ squadCount = 1;
+
+ CBaseEntity *pEntity = NULL;
+
+ if ( m_SquadName != NULL_STRING )
+ {
+ // I have a netname, so unconditionally recruit everyone else with that name.
+ pEntity = gEntList.FindEntityByClassname( pEntity, "monster_human_grunt" );
+
+ while ( pEntity )
+ {
+ CNPC_HGrunt *pRecruit = (CNPC_HGrunt*)pEntity->MyNPCPointer();
+
+ if ( pRecruit )
+ {
+ if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass && pRecruit != this )
+ {
+ // minimum protection here against user error.in worldcraft.
+ if ( pRecruit->m_SquadName != NULL_STRING && FStrEq( STRING( m_SquadName ), STRING( pRecruit->m_SquadName ) ) )
+ {
+ pRecruit->InitSquad();
+ squadCount++;
+ }
+ }
+ }
+
+ pEntity = gEntList.FindEntityByClassname( pEntity, "monster_human_grunt" );
+ }
+
+ return squadCount;
+ }
+ else
+ {
+ char szSquadName[64];
+ Q_snprintf( szSquadName, sizeof( szSquadName ), "squad%d\n", g_iSquadIndex );
+
+ m_SquadName = MAKE_STRING( szSquadName );
+
+ while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), searchRadius ) ) != NULL )
+ {
+ if ( !FClassnameIs ( pEntity, "monster_human_grunt" ) )
+ continue;
+
+ CNPC_HGrunt *pRecruit = (CNPC_HGrunt*)pEntity->MyNPCPointer();
+
+ if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_hCine )
+ {
+ // Can we recruit this guy?
+ if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass &&
+ ( (iMyClass != CLASS_ALIEN_MONSTER) || FClassnameIs( this, pRecruit->GetClassname() ) ) &&
+ !pRecruit->m_SquadName )
+ {
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin() + GetViewOffset(), pRecruit->GetAbsOrigin() + GetViewOffset(), MASK_NPCSOLID_BRUSHONLY, pRecruit, COLLISION_GROUP_NONE, &tr );// try to hit recruit with a traceline.
+
+ if ( tr.fraction == 1.0 )
+ {
+ //We're ready to recruit people, so start a squad if I don't have one.
+ if ( !m_pSquad )
+ {
+ InitSquad();
+ }
+
+ pRecruit->m_SquadName = m_SquadName;
+
+ pRecruit->CapabilitiesAdd ( bits_CAP_SQUAD );
+ pRecruit->InitSquad();
+
+ squadCount++;
+ }
+ }
+ }
+ }
+
+ if ( squadCount > 1 )
+ {
+ g_iSquadIndex++;
+ }
+ }
+
+ return squadCount;
+}
+
+void CNPC_HGrunt::StartNPC ( void )
+{
+ if ( !m_pSquad )
+ {
+ if ( m_SquadName != NULL_STRING )
+ {
+ // if I have a groupname, I can only recruit if I'm flagged as leader
+ if ( GetSpawnFlags() & SF_GRUNT_LEADER )
+ {
+ InitSquad();
+
+ // try to form squads now.
+ int iSquadSize = SquadRecruit( 1024, 4 );
+
+ if ( iSquadSize )
+ {
+ Msg ( "Squad of %d %s formed\n", iSquadSize, GetClassname() );
+ }
+ }
+ else
+ {
+
+ //Hacky.
+ //Revisit me later.
+ const char *pSquadName = STRING( m_SquadName );
+
+ m_SquadName = NULL_STRING;
+
+ BaseClass::StartNPC();
+
+ m_SquadName = MAKE_STRING( pSquadName );
+
+ return;
+ }
+ }
+ else
+ {
+ int iSquadSize = SquadRecruit( 1024, 4 );
+
+ if ( iSquadSize )
+ {
+ Msg ( "Squad of %d %s formed\n", iSquadSize, GetClassname() );
+ }
+ }
+ }
+
+ BaseClass::StartNPC();
+
+ if ( m_pSquad && m_pSquad->IsLeader( this ) )
+ {
+ SetBodygroup( 1, 1 ); // UNDONE: truly ugly hack
+ m_nSkin = 0;
+ }
+}
+
+//=========================================================
+// CheckMeleeAttack1
+//=========================================================
+int CNPC_HGrunt::MeleeAttack1Conditions ( float flDot, float flDist )
+{
+ if (flDist > 64)
+ return COND_TOO_FAR_TO_ATTACK;
+ else if (flDot < 0.7)
+ return COND_NOT_FACING_ATTACK;
+
+ return COND_CAN_MELEE_ATTACK1;
+}
+
+//=========================================================
+// CheckRangeAttack1 - overridden for HGrunt, cause
+// FCanCheckAttacks() doesn't disqualify all attacks based
+// on whether or not the enemy is occluded because unlike
+// the base class, the HGrunt can attack when the enemy is
+// occluded (throw grenade over wall, etc). We must
+// disqualify the machine gun attack if the enemy is occluded.
+//=========================================================
+int CNPC_HGrunt::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( !HasCondition( COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() )
+ {
+ trace_t tr;
+
+ if ( !GetEnemy()->IsPlayer() && flDist <= 64 )
+ {
+ // kick nonclients, but don't shoot at them.
+ return COND_NONE;
+ }
+
+ Vector vecSrc;
+ QAngle angAngles;
+
+ GetAttachment( "0", vecSrc, angAngles );
+
+ //NDebugOverlay::Line( GetAbsOrigin() + GetViewOffset(), GetEnemy()->BodyTarget(GetAbsOrigin() + GetViewOffset()), 255, 0, 0, false, 0.1 );
+ // verify that a bullet fired from the gun will hit the enemy before the world.
+ UTIL_TraceLine( GetAbsOrigin() + GetViewOffset(), GetEnemy()->BodyTarget(GetAbsOrigin() + GetViewOffset()), MASK_SHOT, this/*pentIgnore*/, COLLISION_GROUP_NONE, &tr);
+
+ if ( tr.fraction == 1.0 || tr.m_pEnt == GetEnemy() )
+ {
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 0, 255, 0, false, 1.0 );
+ return COND_CAN_RANGE_ATTACK1;
+ }
+
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 0, 0, false, 1.0 );
+ }
+
+
+ if ( !NoFriendlyFire() )
+ return COND_WEAPON_BLOCKED_BY_FRIEND; //err =|
+
+ return COND_NONE;
+}
+
+int CNPC_HGrunt::RangeAttack2Conditions( float flDot, float flDist )
+{
+ m_iLastGrenadeCondition = GetGrenadeConditions( flDot, flDist );
+ return m_iLastGrenadeCondition;
+}
+
+int CNPC_HGrunt::GetGrenadeConditions( float flDot, float flDist )
+{
+ if ( !FBitSet( m_iWeapons, ( HGRUNT_HANDGRENADE | HGRUNT_GRENADELAUNCHER ) ) )
+ return COND_NONE;
+
+ // assume things haven't changed too much since last time
+ if (gpGlobals->curtime < m_flNextGrenadeCheck )
+ return m_iLastGrenadeCondition;
+
+ if ( m_flGroundSpeed != 0 )
+ return COND_NONE;
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if (!pEnemy)
+ return COND_NONE;
+
+ Vector flEnemyLKP = GetEnemyLKP();
+ if ( !(pEnemy->GetFlags() & FL_ONGROUND) && pEnemy->GetWaterLevel() == 0 && flEnemyLKP.z > (GetAbsOrigin().z + WorldAlignMaxs().z) )
+ {
+ //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to
+ // be grenaded.
+ // don't throw grenades at anything that isn't on the ground!
+ return COND_NONE;
+ }
+
+ Vector vecTarget;
+
+ if (FBitSet( m_iWeapons, HGRUNT_HANDGRENADE))
+ {
+ // find feet
+ if ( random->RandomInt( 0,1 ) )
+ {
+ // magically know where they are
+ pEnemy->CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.0f ), &vecTarget );
+ }
+ else
+ {
+ // toss it to where you last saw them
+ vecTarget = flEnemyLKP;
+ }
+ }
+ else
+ {
+ // find target
+ // vecTarget = GetEnemy()->BodyTarget( GetAbsOrigin() );
+ vecTarget = GetEnemy()->GetAbsOrigin() + (GetEnemy()->BodyTarget( GetAbsOrigin() ) - GetEnemy()->GetAbsOrigin());
+ // estimate position
+ if ( HasCondition( COND_SEE_ENEMY))
+ {
+ vecTarget = vecTarget + ((vecTarget - GetAbsOrigin()).Length() / sk_hgrunt_gspeed.GetFloat()) * GetEnemy()->GetAbsVelocity();
+ }
+ }
+
+ // are any of my squad members near the intended grenade impact area?
+ if ( m_pSquad )
+ {
+ if ( m_pSquad->SquadMemberInRange( vecTarget, 256 ) )
+ {
+ // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
+ return COND_NONE;
+ }
+ }
+
+ if ( ( vecTarget - GetAbsOrigin() ).Length2D() <= 256 )
+ {
+ // crap, I don't want to blow myself up
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
+ return COND_NONE;
+ }
+
+
+ if (FBitSet( m_iWeapons, HGRUNT_HANDGRENADE))
+ {
+ Vector vGunPos;
+ QAngle angGunAngles;
+ GetAttachment( "0", vGunPos, angGunAngles );
+
+
+ Vector vecToss = VecCheckToss( this, vGunPos, vecTarget, -1, 0.5, false );
+
+ if ( vecToss != vec3_origin )
+ {
+ m_vecTossVelocity = vecToss;
+
+ // don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 0.3; // 1/3 second.
+
+ return COND_CAN_RANGE_ATTACK2;
+ }
+ else
+ {
+ // don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
+
+ return COND_NONE;
+ }
+ }
+ else
+ {
+ Vector vGunPos;
+ QAngle angGunAngles;
+ GetAttachment( "0", vGunPos, angGunAngles );
+
+ Vector vecToss = VecCheckThrow( this, vGunPos, vecTarget, sk_hgrunt_gspeed.GetFloat(), 0.5 );
+
+ if ( vecToss != vec3_origin )
+ {
+ m_vecTossVelocity = vecToss;
+
+ // don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 0.3; // 1/3 second.
+
+ return COND_CAN_RANGE_ATTACK2;
+ }
+ else
+ {
+ // don't check again for a while.
+ m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second.
+
+ return COND_NONE;
+ }
+ }
+}
+
+
+//=========================================================
+// FCanCheckAttacks - this is overridden for human grunts
+// because they can throw/shoot grenades when they can't see their
+// target and the base class doesn't check attacks if the monster
+// cannot see its enemy.
+//
+// !!!BUGBUG - this gets called before a 3-round burst is fired
+// which means that a friendly can still be hit with up to 2 rounds.
+// ALSO, grenades will not be tossed if there is a friendly in front,
+// this is a bad bug. Friendly machine gun fire avoidance
+// will unecessarily prevent the throwing of a grenade as well.
+//=========================================================
+bool CNPC_HGrunt::FCanCheckAttacks( void )
+{
+ // This condition set when too close to a grenade to blow it up
+ if ( !HasCondition( COND_TOO_CLOSE_TO_ATTACK ) )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+int CNPC_HGrunt::GetSoundInterests( void )
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_PLAYER |
+ SOUND_BULLET_IMPACT |
+ SOUND_DANGER;
+}
+
+
+//=========================================================
+// TraceAttack - make sure we're not taking it in the helmet
+//=========================================================
+void CNPC_HGrunt::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ // check for helmet shot
+ if (ptr->hitgroup == 11)
+ {
+ // make sure we're wearing one
+ if ( GetBodygroup( 1 ) == HEAD_GRUNT && (info.GetDamageType() & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB)))
+ {
+ // absorb damage
+ info.SetDamage( info.GetDamage() - 20 );
+ if ( info.GetDamage() <= 0 )
+ info.SetDamage( 0.01 );
+ }
+ // it's head shot anyways
+ ptr->hitgroup = HITGROUP_HEAD;
+ }
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+
+//=========================================================
+// TakeDamage - overridden for the grunt because the grunt
+// needs to forget that he is in cover if he's hurt. (Obviously
+// not in a safe place anymore).
+//=========================================================
+int CNPC_HGrunt::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ Forget( bits_MEMORY_INCOVER );
+
+ return BaseClass::OnTakeDamage_Alive ( inputInfo );
+}
+
+
+//=========================================================
+// SetYawSpeed - allows each sequence to have a different
+// turn rate associated with it.
+//=========================================================
+float CNPC_HGrunt::MaxYawSpeed( void )
+{
+ float flYS;
+
+ switch ( GetActivity() )
+ {
+ case ACT_IDLE:
+ flYS = 150;
+ break;
+ case ACT_RUN:
+ flYS = 150;
+ break;
+ case ACT_WALK:
+ flYS = 180;
+ break;
+ case ACT_RANGE_ATTACK1:
+ flYS = 120;
+ break;
+ case ACT_RANGE_ATTACK2:
+ flYS = 120;
+ break;
+ case ACT_MELEE_ATTACK1:
+ flYS = 120;
+ break;
+ case ACT_MELEE_ATTACK2:
+ flYS = 120;
+ break;
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ flYS = 180;
+ break;
+ case ACT_GLIDE:
+ case ACT_FLY:
+ flYS = 30;
+ break;
+ default:
+ flYS = 90;
+ break;
+ }
+
+ // Yaw speed is handled differently now!
+ return flYS * 0.5f;
+}
+
+void CNPC_HGrunt::IdleSound( void )
+{
+ if (FOkToSpeak() && ( g_fGruntQuestion || random->RandomInt( 0,1 ) ) )
+ {
+ if (!g_fGruntQuestion)
+ {
+ // ask question or make statement
+ switch ( random->RandomInt( 0,2 ) )
+ {
+ case 0: // check in
+ SENTENCEG_PlayRndSz( edict(), "HG_CHECK", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ g_fGruntQuestion = 1;
+ break;
+ case 1: // question
+ SENTENCEG_PlayRndSz( edict(), "HG_QUEST", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ g_fGruntQuestion = 2;
+ break;
+ case 2: // statement
+ SENTENCEG_PlayRndSz( edict(), "HG_IDLE", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ break;
+ }
+ }
+ else
+ {
+ switch (g_fGruntQuestion)
+ {
+ case 1: // check in
+ SENTENCEG_PlayRndSz( edict(), "HG_CLEAR", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ break;
+ case 2: // question
+ SENTENCEG_PlayRndSz( edict(), "HG_ANSWER", HGRUNT_SENTENCE_VOLUME, SNDLVL_NORM, 0, m_voicePitch);
+ break;
+ }
+ g_fGruntQuestion = 0;
+ }
+ JustSpoke();
+ }
+}
+
+bool CNPC_HGrunt::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
+{
+ if (interactionType == g_interactionBarnacleVictimDangle)
+ {
+ // Force choosing of a new schedule
+ ClearSchedule( "Soldier being eaten by a barnacle" );
+ m_bInBarnacleMouth = true;
+ return true;
+ }
+ else if ( interactionType == g_interactionBarnacleVictimReleased )
+ {
+ SetState ( NPC_STATE_IDLE );
+ m_bInBarnacleMouth = false;
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_STEP );
+ return true;
+ }
+ else if ( interactionType == g_interactionBarnacleVictimGrab )
+ {
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ SetGroundEntity( NULL );
+ }
+
+ //Maybe this will break something else.
+ if ( GetState() == NPC_STATE_SCRIPT )
+ {
+ m_hCine->CancelScript();
+ ClearSchedule( "Soldier grabbed by a barnacle" );
+ }
+
+ SetState( NPC_STATE_PRONE );
+
+ CTakeDamageInfo info;
+ PainSound( info );
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Combine needs to check ammo
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CNPC_HGrunt::CheckAmmo ( void )
+{
+ if ( m_cAmmoLoaded <= 0 )
+ SetCondition( COND_NO_PRIMARY_AMMO );
+
+}
+
+//=========================================================
+//=========================================================
+CBaseEntity *CNPC_HGrunt::Kick( void )
+{
+ trace_t tr;
+
+ Vector forward;
+ AngleVectors( GetAbsAngles(), &forward );
+ Vector vecStart = GetAbsOrigin();
+ vecStart.z += WorldAlignSize().z * 0.5;
+ Vector vecEnd = vecStart + (forward * 70);
+
+ UTIL_TraceHull( vecStart, vecEnd, Vector(-16,-16,-18), Vector(16,16,18), MASK_SHOT_HULL, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.m_pEnt )
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+ return pEntity;
+ }
+
+ return NULL;
+}
+
+Vector CNPC_HGrunt::Weapon_ShootPosition( void )
+{
+ if ( m_fStanding )
+ return GetAbsOrigin() + Vector( 0, 0, 60 );
+ else
+ return GetAbsOrigin() + Vector( 0, 0, 48 );
+}
+
+void CNPC_HGrunt::Event_Killed( const CTakeDamageInfo &info )
+{
+ Vector vecGunPos;
+ QAngle vecGunAngles;
+
+ GetAttachment( "0", vecGunPos, vecGunAngles );
+
+ // switch to body group with no gun.
+ SetBodygroup( GUN_GROUP, GUN_NONE );
+
+ // If the gun would drop into a wall, spawn it at our origin
+ if( UTIL_PointContents( vecGunPos ) & CONTENTS_SOLID )
+ {
+ vecGunPos = GetAbsOrigin();
+ }
+
+ // now spawn a gun.
+ if (FBitSet( m_iWeapons, HGRUNT_SHOTGUN ))
+ {
+ DropItem( "weapon_shotgun", vecGunPos, vecGunAngles );
+ }
+ else
+ {
+ DropItem( "weapon_mp5", vecGunPos, vecGunAngles );
+ }
+
+ if (FBitSet( m_iWeapons, HGRUNT_GRENADELAUNCHER ))
+ {
+ DropItem( "ammo_ARgrenades", BodyTarget( GetAbsOrigin() ), vecGunAngles );
+ }
+
+ BaseClass::Event_Killed( info );
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_HGrunt::HandleAnimEvent( animevent_t *pEvent )
+{
+ Vector vecShootDir;
+ Vector vecShootOrigin;
+
+ switch( pEvent->event )
+ {
+ case HGRUNT_AE_RELOAD:
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HGrunt.Reload" );
+
+ m_cAmmoLoaded = m_iClipSize;
+ ClearCondition( COND_NO_PRIMARY_AMMO);
+ }
+ break;
+
+ case HGRUNT_AE_GREN_TOSS:
+ {
+ CHandGrenade *pGrenade = (CHandGrenade*)Create( "grenade_hand", GetAbsOrigin() + Vector(0,0,60), vec3_angle );
+ if ( pGrenade )
+ {
+ pGrenade->ShootTimed( this, m_vecTossVelocity, 3.5 );
+ }
+
+ m_iLastGrenadeCondition = COND_NONE;
+ m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
+
+ Msg( "Tossing a grenade to flush you out!\n" );
+ }
+ break;
+
+ case HGRUNT_AE_GREN_LAUNCH:
+ {
+ CPASAttenuationFilter filter2( this );
+ EmitSound( filter2, entindex(), "HGrunt.GrenadeLaunch" );
+
+ Vector vecSrc;
+ QAngle angAngles;
+
+ GetAttachment( "0", vecSrc, angAngles );
+
+ CGrenadeMP5 * m_pMyGrenade = (CGrenadeMP5*)Create( "grenade_mp5", vecSrc, angAngles, this );
+ m_pMyGrenade->SetAbsVelocity( m_vecTossVelocity );
+ m_pMyGrenade->SetLocalAngularVelocity( QAngle( random->RandomFloat( -100, -500 ), 0, 0 ) );
+ m_pMyGrenade->SetMoveType( MOVETYPE_FLYGRAVITY );
+ m_pMyGrenade->SetThrower( this );
+ m_pMyGrenade->SetDamage( sk_plr_dmg_mp5_grenade.GetFloat() );
+
+ if (g_iSkillLevel == SKILL_HARD)
+ m_flNextGrenadeCheck = gpGlobals->curtime + random->RandomFloat( 2, 5 );// wait a random amount of time before shooting again
+ else
+ m_flNextGrenadeCheck = gpGlobals->curtime + 6;// wait six seconds before even looking again to see if a grenade can be thrown.
+
+ m_iLastGrenadeCondition = COND_NONE;
+
+ Msg( "Using grenade launcer to flush you out!\n" );
+ }
+ break;
+
+ case HGRUNT_AE_GREN_DROP:
+ {
+ CHandGrenade *pGrenade = (CHandGrenade*)Create( "grenade_hand", Weapon_ShootPosition(), vec3_angle );
+ if ( pGrenade )
+ {
+ pGrenade->ShootTimed( this, m_vecTossVelocity, 3.5 );
+ }
+
+ m_iLastGrenadeCondition = COND_NONE;
+ Msg( "Dropping a grenade!\n" );
+ }
+ break;
+
+ case HGRUNT_AE_BURST1:
+ {
+ if ( FBitSet( m_iWeapons, HGRUNT_9MMAR ) )
+ {
+ Shoot();
+
+ CPASAttenuationFilter filter3( this );
+ // the first round of the three round burst plays the sound and puts a sound in the world sound list.
+ EmitSound( filter3, entindex(), "HGrunt.9MM" );
+ }
+ else
+ {
+ Shotgun( );
+
+ CPASAttenuationFilter filter4( this );
+ EmitSound( filter4, entindex(), "HGrunt.Shotgun" );
+ }
+
+ CSoundEnt::InsertSound ( SOUND_COMBAT, GetAbsOrigin(), 384, 0.3 );
+ }
+ break;
+
+ case HGRUNT_AE_BURST2:
+ case HGRUNT_AE_BURST3:
+ Shoot();
+ break;
+
+ case HGRUNT_AE_KICK:
+ {
+ CBaseEntity *pHurt = Kick();
+
+ if ( pHurt )
+ {
+ // SOUND HERE!
+ Vector forward, up;
+ AngleVectors( GetAbsAngles(), &forward, NULL, &up );
+
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ pHurt->ViewPunch( QAngle( 15, 0, 0) );
+
+ // Don't give velocity or damage to the world
+ if( pHurt->entindex() > 0 )
+ {
+ pHurt->ApplyAbsVelocityImpulse( forward * 100 + up * 50 );
+
+ CTakeDamageInfo info( this, this, sk_hgrunt_kick.GetFloat(), DMG_CLUB );
+ CalculateMeleeDamageForce( &info, forward, pHurt->GetAbsOrigin() );
+ pHurt->TakeDamage( info );
+ }
+ }
+ }
+ break;
+
+ case HGRUNT_AE_CAUGHT_ENEMY:
+ {
+ if ( FOkToSpeak() )
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+
+ }
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+void CNPC_HGrunt::SetAim( const Vector &aimDir )
+{
+ QAngle angDir;
+ VectorAngles( aimDir, angDir );
+
+ float curPitch = GetPoseParameter( "XR" );
+ float newPitch = curPitch + UTIL_AngleDiff( UTIL_ApproachAngle( angDir.x, curPitch, 60 ), curPitch );
+
+ SetPoseParameter( "XR", -newPitch );
+}
+
+//=========================================================
+// Shoot
+//=========================================================
+void CNPC_HGrunt::Shoot ( void )
+{
+ if ( GetEnemy() == NULL )
+ return;
+
+ Vector vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin );
+
+ Vector forward, right, up;
+ AngleVectors( GetAbsAngles(), &forward, &right, &up );
+
+ Vector vecShellVelocity = right * random->RandomFloat(40,90) + up * random->RandomFloat( 75,200 ) + forward * random->RandomFloat( -40, 40 );
+ EjectShell( vecShootOrigin - vecShootDir * 24, vecShellVelocity, GetAbsAngles().y, 0 );
+ FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, m_iAmmoType ); // shoot +-5 degrees
+
+ DoMuzzleFlash();
+
+ m_cAmmoLoaded--;// take away a bullet!
+
+ SetAim( vecShootDir );
+}
+
+//=========================================================
+// Shoot
+//=========================================================
+void CNPC_HGrunt::Shotgun ( void )
+{
+ if ( GetEnemy() == NULL )
+ return;
+
+ Vector vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin );
+
+ Vector forward, right, up;
+ AngleVectors( GetAbsAngles(), &forward, &right, &up );
+
+ Vector vecShellVelocity = right * random->RandomFloat(40,90) + up * random->RandomFloat( 75,200 ) + forward * random->RandomFloat( -40, 40 );
+ EjectShell( vecShootOrigin - vecShootDir * 24, vecShellVelocity, GetAbsAngles().y, 1 );
+ FireBullets( sk_hgrunt_pellets.GetFloat(), vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, m_iAmmoType, 0 ); // shoot +-7.5 degrees
+
+ DoMuzzleFlash();
+
+ m_cAmmoLoaded--;// take away a bullet!
+
+ SetAim( vecShootDir );
+}
+
+//=========================================================
+// start task
+//=========================================================
+void CNPC_HGrunt::StartTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_GRUNT_CHECK_FIRE:
+ if ( !NoFriendlyFire() )
+ {
+ SetCondition( COND_WEAPON_BLOCKED_BY_FRIEND );
+ }
+ TaskComplete();
+ break;
+
+ case TASK_GRUNT_SPEAK_SENTENCE:
+ SpeakSentence();
+ TaskComplete();
+ break;
+
+ case TASK_WALK_PATH:
+ case TASK_RUN_PATH:
+ // grunt no longer assumes he is covered if he moves
+ Forget( bits_MEMORY_INCOVER );
+ BaseClass ::StartTask( pTask );
+ break;
+
+ case TASK_RELOAD:
+ SetIdealActivity( ACT_RELOAD );
+ break;
+
+ case TASK_GRUNT_FACE_TOSS_DIR:
+ break;
+
+ case TASK_FACE_IDEAL:
+ case TASK_FACE_ENEMY:
+ BaseClass::StartTask( pTask );
+ if (GetMoveType() == MOVETYPE_FLYGRAVITY)
+ {
+ SetIdealActivity( ACT_GLIDE );
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_HGrunt::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_GRUNT_FACE_TOSS_DIR:
+ {
+ // project a point along the toss vector and turn to face that point.
+ GetMotor()->SetIdealYawToTargetAndUpdate( GetAbsOrigin() + m_vecTossVelocity * 64, AI_KEEP_YAW_SPEED );
+
+ if ( FacingIdeal() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+ default:
+ {
+ BaseClass::RunTask( pTask );
+ break;
+ }
+ }
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_HGrunt::PainSound( const CTakeDamageInfo &info )
+{
+ if ( gpGlobals->curtime > m_flNextPainTime )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HGrunt.Pain" );
+
+ m_flNextPainTime = gpGlobals->curtime + 1;
+ }
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_HGrunt::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this, ATTN_IDLE );
+ EmitSound( filter, entindex(), "HGrunt.Die" );
+}
+
+//=========================================================
+// SetActivity
+//=========================================================
+Activity CNPC_HGrunt::NPC_TranslateActivity( Activity eNewActivity )
+{
+ switch ( eNewActivity)
+ {
+ case ACT_RANGE_ATTACK1:
+ // grunt is either shooting standing or shooting crouched
+ if (FBitSet( m_iWeapons, HGRUNT_9MMAR))
+ {
+ if ( m_fStanding )
+ {
+ // get aimable sequence
+ return (Activity)ACT_GRUNT_MP5_STANDING;
+ }
+ else
+ {
+ // get crouching shoot
+ return (Activity)ACT_GRUNT_MP5_CROUCHING;
+ }
+ }
+ else
+ {
+ if ( m_fStanding )
+ {
+ // get aimable sequence
+ return (Activity)ACT_GRUNT_SHOTGUN_STANDING;
+ }
+ else
+ {
+ // get crouching shoot
+ return (Activity)ACT_GRUNT_SHOTGUN_CROUCHING;
+ }
+ }
+ break;
+ case ACT_RANGE_ATTACK2:
+ // grunt is going to a secondary long range attack. This may be a thrown
+ // grenade or fired grenade, we must determine which and pick proper sequence
+ if ( m_iWeapons & HGRUNT_HANDGRENADE )
+ {
+ // get toss anim
+ return (Activity)ACT_GRUNT_TOSS_GRENADE;
+ }
+ else
+ {
+ // get launch anim
+ return (Activity)ACT_GRUNT_LAUNCH_GRENADE;
+ }
+ break;
+ case ACT_RUN:
+ if ( m_iHealth <= HGRUNT_LIMP_HEALTH )
+ {
+ // limp!
+ return ACT_RUN_HURT;
+ }
+ else
+ {
+ return eNewActivity;
+ }
+ break;
+ case ACT_WALK:
+ if ( m_iHealth <= HGRUNT_LIMP_HEALTH )
+ {
+ // limp!
+ return ACT_WALK_HURT;
+ }
+ else
+ {
+ return eNewActivity;
+ }
+ break;
+ case ACT_IDLE:
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ {
+ eNewActivity = ACT_IDLE_ANGRY;
+ }
+
+ break;
+ }
+
+ return BaseClass::NPC_TranslateActivity( eNewActivity );
+}
+
+void CNPC_HGrunt::ClearAttackConditions( void )
+{
+ bool fCanRangeAttack2 = HasCondition( COND_CAN_RANGE_ATTACK2 );
+
+ // Call the base class.
+ BaseClass::ClearAttackConditions();
+
+ if( fCanRangeAttack2 )
+ {
+ // We don't allow the base class to clear this condition because we
+ // don't sense for it every frame.
+ SetCondition( COND_CAN_RANGE_ATTACK2 );
+ }
+}
+
+int CNPC_HGrunt::SelectSchedule( void )
+{
+ // clear old sentence
+ m_iSentence = HGRUNT_SENT_NONE;
+
+ // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling.
+ if ( GetMoveType() == MOVETYPE_FLYGRAVITY && m_NPCState != NPC_STATE_PRONE )
+ {
+ if (GetFlags() & FL_ONGROUND)
+ {
+ // just landed
+ SetMoveType( MOVETYPE_STEP );
+ SetGravity( 1.0 );
+ return SCHED_GRUNT_REPEL_LAND;
+ }
+ else
+ {
+ // repel down a rope,
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ return SCHED_GRUNT_REPEL_ATTACK;
+ else
+ return SCHED_GRUNT_REPEL;
+ }
+ }
+
+ // grunts place HIGH priority on running away from danger sounds.
+ if ( HasCondition ( COND_HEAR_DANGER ) )
+ {
+ // dangerous sound nearby!
+
+ //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby,
+ // and the grunt should find cover from the blast
+ // good place for "SHIT!" or some other colorful verbal indicator of dismay.
+ // It's not safe to play a verbal order here "Scatter", etc cause
+ // this may only affect a single individual in a squad.
+
+ if (FOkToSpeak())
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+ }
+
+ switch ( m_NPCState )
+ {
+
+ case NPC_STATE_PRONE:
+ {
+ if (m_bInBarnacleMouth)
+ {
+ return SCHED_GRUNT_BARNACLE_CHOMP;
+ }
+ else
+ {
+ return SCHED_GRUNT_BARNACLE_HIT;
+ }
+ }
+
+ case NPC_STATE_COMBAT:
+ {
+// dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // call base class, all code to handle dead enemies is centralized there.
+ return BaseClass::SelectSchedule();
+ }
+
+// new enemy
+ if ( HasCondition( COND_NEW_ENEMY) )
+ {
+ if ( m_pSquad )
+ {
+ if ( !m_pSquad->IsLeader( this ) )
+ {
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ else
+ {
+ //!!!KELLY - the leader of a squad of grunts has just seen the player or a
+ // monster and has made it the squad's enemy. You
+ // can check pev->flags for FL_CLIENT to determine whether this is the player
+ // or a monster. He's going to immediately start
+ // firing, though. If you'd like, we can make an alternate "first sight"
+ // schedule where the leader plays a handsign anim
+ // that gives us enough time to hear a short sentence or spoken command
+ // before he starts pluggin away.
+ if (FOkToSpeak())// && RANDOM_LONG(0,1))
+ {
+ if ((GetEnemy() != NULL) && GetEnemy()->IsPlayer())
+ // player
+ SENTENCEG_PlayRndSz( edict(), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ else if ((GetEnemy() != NULL) &&
+ (GetEnemy()->Classify() != CLASS_PLAYER_ALLY) &&
+ (GetEnemy()->Classify() != CLASS_HUMAN_PASSIVE) &&
+ (GetEnemy()->Classify() != CLASS_MACHINE) )
+ // monster
+ SENTENCEG_PlayRndSz( edict(), "HG_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+
+ JustSpoke();
+ }
+
+ if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ return SCHED_GRUNT_SUPPRESS;
+ }
+ else
+ {
+ return SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE;
+ }
+ }
+ }
+ }
+// no ammo
+ else if ( HasCondition ( COND_NO_PRIMARY_AMMO ) )
+ {
+ //!!!KELLY - this individual just realized he's out of bullet ammo.
+ // He's going to try to find cover to run to and reload, but rarely, if
+ // none is available, he'll drop and reload in the open here.
+ return SCHED_GRUNT_HIDE_RELOAD;
+ }
+
+// damaged just a little
+ else if ( HasCondition( COND_LIGHT_DAMAGE ) )
+ {
+ // if hurt:
+ // 90% chance of taking cover
+ // 10% chance of flinch.
+ int iPercent = random->RandomInt(0,99);
+
+ if ( iPercent <= 90 && GetEnemy() != NULL )
+ {
+ // only try to take cover if we actually have an enemy!
+
+ //!!!KELLY - this grunt was hit and is going to run to cover.
+ if (FOkToSpeak()) // && RANDOM_LONG(0,1))
+ {
+ //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ m_iSentence = HGRUNT_SENT_COVER;
+ //JustSpoke();
+ }
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ else
+ {
+ return SCHED_SMALL_FLINCH;
+ }
+ }
+// can kick
+ else if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return SCHED_MELEE_ATTACK1;
+ }
+// can grenade launch
+
+ else if ( FBitSet( m_iWeapons, HGRUNT_GRENADELAUNCHER) && HasCondition ( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_GRENADE2 ) )
+ {
+ // shoot a grenade if you can
+ return SCHED_RANGE_ATTACK2;
+ }
+// can shoot
+ else if ( HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ if ( m_pSquad )
+ {
+ if ( m_pSquad->GetLeader() != NULL )
+ {
+
+ CAI_BaseNPC *pSquadLeader = m_pSquad->GetLeader()->MyNPCPointer();
+
+ // if the enemy has eluded the squad and a squad member has just located the enemy
+ // and the enemy does not see the squad member, issue a call to the squad to waste a
+ // little time and give the player a chance to turn.
+ if ( pSquadLeader && pSquadLeader->EnemyHasEludedMe() && !HasCondition ( COND_ENEMY_FACING_ME ) )
+ {
+ return SCHED_GRUNT_FOUND_ENEMY;
+ }
+ }
+ }
+
+ if ( OccupyStrategySlotRange ( SQUAD_SLOT_ENGAGE1, SQUAD_SLOT_ENGAGE2 ) )
+ {
+ // try to take an available ENGAGE slot
+ return SCHED_RANGE_ATTACK1;
+ }
+ else if ( HasCondition ( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_GRENADE2 ) )
+ {
+ // throw a grenade if can and no engage slots are available
+ return SCHED_RANGE_ATTACK2;
+ }
+ else
+ {
+ // hide!
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ }
+// can't see enemy
+ else if ( HasCondition( COND_ENEMY_OCCLUDED ) )
+ {
+ if ( HasCondition( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_GRENADE2 ) )
+ {
+ //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc
+ if (FOkToSpeak())
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+ return SCHED_RANGE_ATTACK2;
+ }
+ else if ( OccupyStrategySlotRange ( SQUAD_SLOT_ENGAGE1, SQUAD_SLOT_ENGAGE2 ) )
+ {
+ //!!!KELLY - grunt cannot see the enemy and has just decided to
+ // charge the enemy's position.
+ if (FOkToSpeak())// && RANDOM_LONG(0,1))
+ {
+ //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ m_iSentence = HGRUNT_SENT_CHARGE;
+ //JustSpoke();
+ }
+
+ return SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE;
+ }
+ else
+ {
+ //!!!KELLY - grunt is going to stay put for a couple seconds to see if
+ // the enemy wanders back out into the open, or approaches the
+ // grunt's covered position. Good place for a taunt, I guess?
+ if (FOkToSpeak() && random->RandomInt(0,1))
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+ return SCHED_STANDOFF;
+ }
+ }
+
+ if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition ( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ return SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE;
+ }
+ }
+ case NPC_STATE_ALERT:
+ if ( HasCondition( COND_ENEMY_DEAD ) && SelectWeightedSequence( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE )
+ {
+ // Scan around for new enemies
+ return SCHED_VICTORY_DANCE;
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+int CNPC_HGrunt::TranslateSchedule( int scheduleType )
+{
+
+ if ( scheduleType == SCHED_CHASE_ENEMY_FAILED )
+ {
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+ }
+ switch ( scheduleType )
+ {
+ case SCHED_TAKE_COVER_FROM_ENEMY:
+ {
+ if ( m_pSquad )
+ {
+ if ( g_iSkillLevel == SKILL_HARD && HasCondition( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlotRange( SQUAD_SLOT_GRENADE1, SQUAD_SLOT_GRENADE2 ) )
+ {
+ if (FOkToSpeak())
+ {
+ SENTENCEG_PlayRndSz( edict(), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_SNDLVL, 0, m_voicePitch);
+ JustSpoke();
+ }
+ return SCHED_GRUNT_TOSS_GRENADE_COVER;
+ }
+ else
+ {
+ return SCHED_GRUNT_TAKE_COVER;
+ }
+ }
+ else
+ {
+ if ( random->RandomInt(0,1) )
+ {
+ return SCHED_GRUNT_TAKE_COVER;
+ }
+ else
+ {
+ return SCHED_GRUNT_GRENADE_COVER;
+ }
+ }
+ }
+ case SCHED_GRUNT_TAKE_COVER_FAILED:
+ {
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) && OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) )
+ {
+ return SCHED_RANGE_ATTACK1;
+ }
+
+ return SCHED_FAIL;
+ }
+ break;
+
+ case SCHED_RANGE_ATTACK1:
+ {
+ // randomly stand or crouch
+ if ( random->RandomInt( 0,9 ) == 0)
+ {
+ m_fStanding = random->RandomInt( 0, 1 ) != 0;
+ }
+
+ if ( m_fStanding )
+ return SCHED_GRUNT_RANGE_ATTACK1B;
+ else
+ return SCHED_GRUNT_RANGE_ATTACK1A;
+ }
+
+ case SCHED_RANGE_ATTACK2:
+ {
+ return SCHED_GRUNT_RANGE_ATTACK2;
+ }
+ case SCHED_VICTORY_DANCE:
+ {
+ if ( m_pSquad )
+ {
+ if ( !m_pSquad->IsLeader( this ) )
+ {
+ return SCHED_GRUNT_FAIL;
+ }
+ }
+
+ return SCHED_GRUNT_VICTORY_DANCE;
+ }
+ case SCHED_GRUNT_SUPPRESS:
+ {
+ if ( GetEnemy()->IsPlayer() && m_fFirstEncounter )
+ {
+ m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy
+ return SCHED_GRUNT_SIGNAL_SUPPRESS;
+ }
+ else
+ {
+ return SCHED_GRUNT_SUPPRESS;
+ }
+ }
+ case SCHED_FAIL:
+ {
+ if ( GetEnemy() != NULL )
+ {
+ // grunt has an enemy, so pick a different default fail schedule most likely to help recover.
+ return SCHED_GRUNT_COMBAT_FAIL;
+ }
+
+ return SCHED_GRUNT_FAIL;
+ }
+ case SCHED_GRUNT_REPEL:
+ {
+ Vector vecVel = GetAbsVelocity();
+ if ( vecVel.z > -128 )
+ {
+ vecVel.z -= 32;
+ SetAbsVelocity( vecVel );
+ }
+
+ return SCHED_GRUNT_REPEL;
+ }
+ case SCHED_GRUNT_REPEL_ATTACK:
+ {
+ Vector vecVel = GetAbsVelocity();
+ if ( vecVel.z > -128 )
+ {
+ vecVel.z -= 32;
+ SetAbsVelocity( vecVel );
+ }
+
+ return SCHED_GRUNT_REPEL_ATTACK;
+ }
+ default:
+ {
+ return BaseClass::TranslateSchedule( scheduleType );
+ }
+ }
+}
+
+//=========================================================
+// CHGruntRepel - when triggered, spawns a monster_human_grunt
+// repelling down a line.
+//=========================================================
+
+class CNPC_HGruntRepel:public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_HGruntRepel, CAI_BaseNPC );
+public:
+ void Spawn( void );
+ void Precache( void );
+ void RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ int m_iSpriteTexture; // Don't save, precache
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( monster_grunt_repel, CNPC_HGruntRepel );
+
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_HGruntRepel )
+ DEFINE_USEFUNC( RepelUse ),
+ //DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
+END_DATADESC()
+
+void CNPC_HGruntRepel::Spawn( void )
+{
+ Precache( );
+ SetSolid( SOLID_NONE );
+
+ SetUse( &CNPC_HGruntRepel::RepelUse );
+}
+
+void CNPC_HGruntRepel::Precache( void )
+{
+ UTIL_PrecacheOther( "monster_human_grunt" );
+ m_iSpriteTexture = PrecacheModel( "sprites/rope.vmt" );
+}
+
+void CNPC_HGruntRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -4096.0), MASK_NPCSOLID, this,COLLISION_GROUP_NONE, &tr);
+
+ CBaseEntity *pEntity = Create( "monster_human_grunt", GetAbsOrigin(), GetAbsAngles() );
+ CAI_BaseNPC *pGrunt = pEntity->MyNPCPointer( );
+ pGrunt->SetMoveType( MOVETYPE_FLYGRAVITY );
+ pGrunt->SetGravity( 0.001 );
+ pGrunt->SetAbsVelocity( Vector( 0, 0, random->RandomFloat( -196, -128 ) ) );
+ pGrunt->SetActivity( ACT_GLIDE );
+ // UNDONE: position?
+ pGrunt->m_vecLastPosition = tr.endpos;
+
+ CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.vmt", 10 );
+ pBeam->PointEntInit( GetAbsOrigin() + Vector(0,0,112), pGrunt );
+ pBeam->SetBeamFlags( FBEAM_SOLID );
+ pBeam->SetColor( 255, 255, 255 );
+ pBeam->SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + -4096.0 * tr.fraction / pGrunt->GetAbsVelocity().z + 0.5 );
+
+ UTIL_Remove( this );
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+AI_BEGIN_CUSTOM_NPC( monster_human_grunt, CNPC_HGrunt )
+
+ DECLARE_ACTIVITY( ACT_GRUNT_LAUNCH_GRENADE )
+ DECLARE_ACTIVITY( ACT_GRUNT_TOSS_GRENADE )
+ DECLARE_ACTIVITY( ACT_GRUNT_MP5_STANDING );
+ DECLARE_ACTIVITY( ACT_GRUNT_MP5_CROUCHING );
+ DECLARE_ACTIVITY( ACT_GRUNT_SHOTGUN_STANDING );
+ DECLARE_ACTIVITY( ACT_GRUNT_SHOTGUN_CROUCHING );
+
+ DECLARE_CONDITION( COND_GRUNT_NOFIRE )
+
+ DECLARE_TASK( TASK_GRUNT_FACE_TOSS_DIR )
+ DECLARE_TASK( TASK_GRUNT_SPEAK_SENTENCE )
+ DECLARE_TASK( TASK_GRUNT_CHECK_FIRE )
+
+ DECLARE_SQUADSLOT( SQUAD_SLOT_GRENADE1 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_GRENADE2 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE1 )
+ DECLARE_SQUADSLOT( SQUAD_SLOT_ENGAGE2 )
+
+ //=========================================================
+ // > SCHED_GRUNT_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 0"
+ " TASK_WAIT_PVS 0"
+ " "
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_COMBAT_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_COMBAT_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 2"
+ " TASK_WAIT_PVS 0"
+ " "
+ " Interrupts"
+ " COND_CAN_RANGE_ATTACK1"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_VICTORY_DANCE
+ // Victory dance!
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_VICTORY_DANCE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_WAIT 1.5"
+ " TASK_GET_PATH_TO_ENEMY_CORPSE 0"
+ " TASK_WALK_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_VICTORY_DANCE"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE
+ // Establish line of fire - move to a position that allows
+ // the grunt to attack.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE_RETRY"
+ " TASK_GET_PATH_TO_ENEMY 0"
+ " TASK_GRUNT_SPEAK_SENTENCE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // This is a schedule I added that borrows some HL2 technology
+ // to be smarter in cases where HL1 was pretty dumb. I've wedged
+ // this between ESTABLISH_LINE_OF_FIRE and TAKE_COVER_FROM_ENEMY (sjb)
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE_RETRY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GRUNT_TAKE_COVER_FROM_ENEMY"
+ " TASK_GET_PATH_TO_ENEMY_LKP_LOS 0"
+ " TASK_GRUNT_SPEAK_SENTENCE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK2"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_FOUND_ENEMY
+ // Grunt established sight with an enemy
+ // that was hiding from the squad.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_FOUND_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_SIGNAL1"
+ " "
+ " Interrupts"
+ " COND_HEAR_DANGER"
+ )
+
+
+ //=========================================================
+ // > SCHED_GRUNT_COMBAT_FACE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_COMBAT_FACE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_FACE_ENEMY 0"
+ " TASK_WAIT 1.5"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_SWEEP"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ )
+
+
+ //=========================================================
+ // > SCHED_GRUNT_SIGNAL_SUPPRESS
+ // Suppressing fire - don't stop shooting until the clip is
+ // empty or grunt gets hurt.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_SIGNAL_SUPPRESS,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_SIGNAL2"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_GRUNT_NOFIRE"
+ " COND_NO_PRIMARY_AMMO"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_SUPPRESS
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_SUPPRESS,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_GRUNT_NOFIRE"
+ " COND_NO_PRIMARY_AMMO"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_WAIT_IN_COVER
+ // grunt wait in cover - we don't allow danger or the ability
+ // to attack to break a grunt's run to cover schedule, but
+ // when a grunt is in cover, we do want them to attack if they can.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_WAIT_IN_COVER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_FACE_ENEMY 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_MELEE_ATTACK2"
+ )
+
+
+ //=========================================================
+ // > SCHED_GRUNT_TAKE_COVER
+ // !!!BUGBUG - set a decent fail schedule here.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_TAKE_COVER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GRUNT_TAKE_COVER_FAILED"
+ " TASK_WAIT 0.2"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_GRUNT_SPEAK_SENTENCE 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_WAIT_IN_COVER"
+ " "
+ " Interrupts"
+ )
+
+
+ //=========================================================
+ // > SCHED_GRUNT_GRENADE_COVER
+ // drop grenade then run to cover.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_GRENADE_COVER,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FIND_COVER_FROM_ENEMY 99"
+ " TASK_FIND_FAR_NODE_COVER_FROM_ENEMY 384"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_SPECIAL_ATTACK1"
+ " TASK_CLEAR_MOVE_WAIT 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_WAIT_IN_COVER"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_TOSS_GRENADE_COVER
+ // drop grenade then run to cover.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_TOSS_GRENADE_COVER,
+
+ " Tasks"
+ " TASK_FACE_ENEMY 0"
+ " TASK_RANGE_ATTACK2 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_TAKE_COVER_FROM_ENEMY"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_HIDE_RELOAD
+ // Grunt reload schedule
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_HIDE_RELOAD,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GRUNT_RELOAD"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RELOAD"
+ " "
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_SWEEP
+ // Do a turning sweep of the area
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_SWEEP,
+
+ " Tasks"
+ " TASK_TURN_LEFT 179"
+ " TASK_WAIT 1"
+ " TASK_TURN_LEFT 179"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK2"
+ " COND_HEAR_WORLD"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_RANGE_ATTACK1A
+ // primary range attack. Overriden because base class stops attacking when the enemy is occluded.
+ // grunt's grenade toss requires the enemy be occluded.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_RANGE_ATTACK1A,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_CROUCH"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_HEAVY_DAMAGE"
+ " COND_ENEMY_OCCLUDED"
+ " COND_HEAR_DANGER"
+ " COND_GRUNT_NOFIRE"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_RANGE_ATTACK1B
+ // primary range attack. Overriden because base class stops attacking when the enemy is occluded.
+ // grunt's grenade toss requires the enemy be occluded.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_RANGE_ATTACK1B,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_IDLE_ANGRY"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_GRUNT_CHECK_FIRE 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_HEAVY_DAMAGE"
+ " COND_ENEMY_OCCLUDED"
+ " COND_HEAR_DANGER"
+ " COND_GRUNT_NOFIRE"
+ " COND_NO_PRIMARY_AMMO"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_RANGE_ATTACK2
+ // secondary range attack. Overriden because base class stops attacking when the enemy is occluded.
+ // grunt's grenade toss requires the enemy be occluded.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_RANGE_ATTACK2,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_GRUNT_FACE_TOSS_DIR 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_WAIT_IN_COVER" // don't run immediately after throwing grenade.
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_REPEL
+ // secondary range attack. Overriden because base class stops attacking when the enemy is occluded.
+ // grunt's grenade toss requires the enemy be occluded.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_REPEL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_GLIDE"
+ " "
+ " Interrupts"
+ " COND_SEE_ENEMY"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ " COND_HEAR_COMBAT"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_REPEL_ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_REPEL_ATTACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_FLY"
+ " "
+ " Interrupts"
+ " COND_ENEMY_OCCLUDED"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_REPEL_LAND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_REPEL_LAND,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_LAND"
+ " TASK_GET_PATH_TO_LASTPOSITION 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_CLEAR_LASTPOSITION 0"
+ " "
+ " Interrupts"
+ " COND_SEE_ENEMY"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_PLAYER"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_RELOAD
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_RELOAD,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RELOAD"
+ " "
+ " Interrupts"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_TAKE_COVER_FROM_ENEMY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_TAKE_COVER_FROM_ENEMY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT 0.2"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_REMEMBER MEMORY:INCOVER"
+ " TASK_FACE_ENEMY 0"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_TAKE_COVER_FAILED
+ // special schedule type that forces analysis of conditions and picks
+ // the best possible schedule to recover from this type of failure.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_TAKE_COVER_FAILED,
+ " Tasks"
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_BARNACLE_HIT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_BARNACLE_HIT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_HIT"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_BARNACLE_PULL"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_BARNACLE_PULL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_BARNACLE_PULL,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_PULL"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_BARNACLE_CHOMP
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_BARNACLE_CHOMP,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHOMP"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_GRUNT_BARNACLE_CHEW"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_GRUNT_BARNACLE_CHEW
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_GRUNT_BARNACLE_CHEW,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHEW"
+ )
+
+AI_END_CUSTOM_NPC()
+
+
+//=========================================================
+// DEAD HGRUNT PROP
+//=========================================================
+class CNPC_DeadHGrunt : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_DeadHGrunt, CAI_BaseNPC );
+public:
+ void Spawn( void );
+ Class_T Classify ( void ) { return CLASS_HUMAN_MILITARY; }
+ float MaxYawSpeed( void ) { return 8.0f; }
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+
+ int m_iPose;// which sequence to display -- temporary, don't need to save
+ static char *m_szPoses[3];
+};
+
+char *CNPC_DeadHGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" };
+
+bool CNPC_DeadHGrunt::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "pose" ) )
+ m_iPose = atoi( szValue );
+ else
+ CAI_BaseNPC::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+LINK_ENTITY_TO_CLASS( monster_hgrunt_dead, CNPC_DeadHGrunt );
+
+//=========================================================
+// ********** DeadHGrunt SPAWN **********
+//=========================================================
+void CNPC_DeadHGrunt::Spawn( void )
+{
+ PrecacheModel("models/hgrunt.mdl");
+ SetModel( "models/hgrunt.mdl" );
+
+ ClearEffects();
+ SetSequence( 0 );
+ m_bloodColor = BLOOD_COLOR_RED;
+
+ SetSequence( LookupSequence( m_szPoses[m_iPose] ) );
+
+ if ( GetSequence() == -1 )
+ {
+ Msg ( "Dead hgrunt with bad pose\n" );
+ }
+
+ // Corpses have less health
+ m_iHealth = 8;
+
+ // map old bodies onto new bodies
+ switch( m_nBody )
+ {
+ case 0: // Grunt with Gun
+ m_nBody = 0;
+ m_nSkin = 0;
+ SetBodygroup( HEAD_GROUP, HEAD_GRUNT );
+ SetBodygroup( GUN_GROUP, GUN_MP5 );
+ break;
+ case 1: // Commander with Gun
+ m_nBody = 0;
+ m_nSkin = 0;
+ SetBodygroup( HEAD_GROUP, HEAD_COMMANDER );
+ SetBodygroup( GUN_GROUP, GUN_MP5 );
+ break;
+ case 2: // Grunt no Gun
+ m_nBody = 0;
+ m_nSkin = 0;
+ SetBodygroup( HEAD_GROUP, HEAD_GRUNT );
+ SetBodygroup( GUN_GROUP, GUN_NONE );
+ break;
+ case 3: // Commander no Gun
+ m_nBody = 0;
+ m_nSkin = 0;
+ SetBodygroup( HEAD_GROUP, HEAD_COMMANDER );
+ SetBodygroup( GUN_GROUP, GUN_NONE );
+ break;
+ }
+
+ NPCInitDead();
+}
diff --git a/game/server/hl1/hl1_npc_hgrunt.h b/game/server/hl1/hl1_npc_hgrunt.h
new file mode 100644
index 0000000..af82ebd
--- /dev/null
+++ b/game/server/hl1/hl1_npc_hgrunt.h
@@ -0,0 +1,117 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_HGRUNT_H
+#define NPC_HGRUNT_H
+
+#include "ai_squad.h"
+#include "hl1_ai_basenpc.h"
+
+class CNPC_HGrunt : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_HGrunt, CHL1BaseNPC );
+public:
+
+ void Precache( void );
+ void Spawn( void );
+
+ void JustSpoke( void );
+ void SpeakSentence( void );
+ void PrescheduleThink ( void );
+
+ bool FOkToSpeak( void );
+
+ Class_T Classify ( void );
+ int RangeAttack1Conditions ( float flDot, float flDist );
+ int MeleeAttack1Conditions ( float flDot, float flDist );
+ int RangeAttack2Conditions ( float flDot, float flDist );
+
+ Activity NPC_TranslateActivity( Activity eNewActivity );
+
+ void ClearAttackConditions( void );
+
+ int IRelationPriority( CBaseEntity *pTarget );
+
+ int GetGrenadeConditions ( float flDot, float flDist );
+
+ bool FCanCheckAttacks( void );
+
+ int GetSoundInterests ( void );
+
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+
+ float MaxYawSpeed( void );
+
+ void IdleSound( void );
+
+ void CheckAmmo ( void );
+
+ CBaseEntity *Kick( void );
+
+ Vector Weapon_ShootPosition( void );
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ void Shoot ( void );
+ void Shotgun( void );
+
+ void StartTask ( const Task_t *pTask );
+ void RunTask ( const Task_t *pTask );
+
+ int SelectSchedule( void );
+ int TranslateSchedule( int scheduleType );
+
+
+ void PainSound( const CTakeDamageInfo &info );
+ void DeathSound( const CTakeDamageInfo &info );
+ void SetAim( const Vector &aimDir );
+
+ bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt);
+
+ void StartNPC ( void );
+
+ int SquadRecruit( int searchRadius, int maxMembers );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+
+
+ static const char *pGruntSentences[];
+
+ bool m_bInBarnacleMouth;
+
+public:
+ DECLARE_DATADESC();
+ DEFINE_CUSTOM_AI;
+
+private:
+
+ // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds,
+ // not every server frame.
+ float m_flNextGrenadeCheck;
+ float m_flNextPainTime;
+ float m_flLastEnemySightTime;
+ float m_flTalkWaitTime;
+
+ Vector m_vecTossVelocity;
+
+ int m_iLastGrenadeCondition;
+ bool m_fStanding;
+ bool m_fFirstEncounter;// only put on the handsign show in the squad's first encounter.
+ int m_iClipSize;
+
+ int m_voicePitch;
+
+ int m_iSentence;
+
+ float m_flCheckAttackTime;
+
+ int m_iAmmoType;
+
+ int m_iWeapons;
+};
+
+#endif \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_hornet.cpp b/game/server/hl1/hl1_npc_hornet.cpp
new file mode 100644
index 0000000..d2b09ee
--- /dev/null
+++ b/game/server/hl1/hl1_npc_hornet.cpp
@@ -0,0 +1,400 @@
+//========= 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 "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 "te.h"
+#include "hl1_npc_hornet.h"
+
+int iHornetTrail;
+int iHornetPuff;
+
+LINK_ENTITY_TO_CLASS( hornet, CNPC_Hornet );
+
+extern ConVar sk_npc_dmg_hornet;
+extern ConVar sk_plr_dmg_hornet;
+
+BEGIN_DATADESC( CNPC_Hornet )
+ DEFINE_FIELD( m_flStopAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_iHornetType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flFlySpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flDamage, FIELD_INTEGER ),
+ DEFINE_FIELD( m_vecEnemyLKP, FIELD_POSITION_VECTOR ),
+
+
+ DEFINE_ENTITYFUNC( DieTouch ),
+ DEFINE_THINKFUNC( StartDart ),
+ DEFINE_THINKFUNC( StartTrack ),
+ DEFINE_ENTITYFUNC( DartTouch ),
+ DEFINE_ENTITYFUNC( TrackTouch ),
+ DEFINE_THINKFUNC( TrackTarget ),
+END_DATADESC()
+
+//=========================================================
+//=========================================================
+void CNPC_Hornet::Spawn( void )
+{
+ Precache();
+
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+ m_takedamage = DAMAGE_YES;
+ AddFlag( FL_NPC );
+ m_iHealth = 1;// weak!
+ m_bloodColor = DONT_BLEED;
+
+ if ( g_pGameRules->IsMultiplayer() )
+ {
+ // hornets don't live as long in multiplayer
+ m_flStopAttack = gpGlobals->curtime + 3.5;
+ }
+ else
+ {
+ m_flStopAttack = gpGlobals->curtime + 5.0;
+ }
+
+ m_flFieldOfView = 0.9; // +- 25 degrees
+
+ if ( random->RandomInt ( 1, 5 ) <= 2 )
+ {
+ m_iHornetType = HORNET_TYPE_RED;
+ m_flFlySpeed = HORNET_RED_SPEED;
+ }
+ else
+ {
+ m_iHornetType = HORNET_TYPE_ORANGE;
+ m_flFlySpeed = HORNET_ORANGE_SPEED;
+ }
+
+ SetModel( "models/hornet.mdl" );
+ UTIL_SetSize( this, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) );
+
+ SetTouch( &CNPC_Hornet::DieTouch );
+ SetThink( &CNPC_Hornet::StartTrack );
+
+ if ( GetOwnerEntity() && (GetOwnerEntity()->GetFlags() & FL_CLIENT) )
+ {
+ m_flDamage = sk_plr_dmg_hornet.GetFloat();
+ }
+ else
+ {
+ // no real owner, or owner isn't a client.
+ m_flDamage = sk_npc_dmg_hornet.GetFloat();
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ ResetSequenceInfo();
+
+ m_vecEnemyLKP = vec3_origin;
+}
+
+
+void CNPC_Hornet::Precache()
+{
+ PrecacheModel("models/hornet.mdl");
+
+ iHornetPuff = PrecacheModel( "sprites/muz1.vmt" );
+ iHornetTrail = PrecacheModel("sprites/laserbeam.vmt");
+
+ PrecacheScriptSound( "Hornet.Die" );
+ PrecacheScriptSound( "Hornet.Buzz" );
+}
+
+//=========================================================
+// hornets will never get mad at each other, no matter who the owner is.
+//=========================================================
+Disposition_t CNPC_Hornet::IRelationType( CBaseEntity *pTarget )
+{
+ if ( pTarget->GetModelIndex() == GetModelIndex() )
+ {
+ return D_NU;
+ }
+
+ return BaseClass::IRelationType( pTarget );
+}
+
+//=========================================================
+// ID's Hornet as their owner
+//=========================================================
+Class_T CNPC_Hornet::Classify ( void )
+{
+ if ( GetOwnerEntity() && (GetOwnerEntity()->GetFlags() & FL_CLIENT) )
+ {
+ return CLASS_PLAYER_BIOWEAPON;
+ }
+
+ return CLASS_ALIEN_BIOWEAPON;
+}
+
+//=========================================================
+// StartDart - starts a hornet out just flying straight.
+//=========================================================
+void CNPC_Hornet::StartDart ( void )
+{
+ IgniteTrail();
+
+ SetTouch( &CNPC_Hornet::DartTouch );
+
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 4 );
+}
+
+
+void CNPC_Hornet::DieTouch ( CBaseEntity *pOther )
+{
+ if ( !pOther || !pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) )
+ {
+ return;
+ }
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Hornet.Die" );
+
+ CTakeDamageInfo info( this, GetOwnerEntity(), m_flDamage, DMG_BULLET );
+ CalculateBulletDamageForce( &info, GetAmmoDef()->Index("Hornet"), GetAbsVelocity(), GetAbsOrigin() );
+ pOther->TakeDamage( info );
+
+ m_takedamage = DAMAGE_NO;
+
+ AddEffects( EF_NODRAW );
+
+ AddSolidFlags( FSOLID_NOT_SOLID );// intangible
+
+ UTIL_Remove( this );
+ SetTouch( NULL );
+}
+
+
+//=========================================================
+// StartTrack - starts a hornet out tracking its target
+//=========================================================
+void CNPC_Hornet:: StartTrack ( void )
+{
+ IgniteTrail();
+
+ SetTouch( &CNPC_Hornet::TrackTouch );
+ SetThink( &CNPC_Hornet::TrackTarget );
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+void TE_BeamFollow( IRecipientFilter& filter, float delay,
+ int iEntIndex, int modelIndex, int haloIndex, float life, float width, float endWidth,
+ float fadeLength,float r, float g, float b, float a );
+
+void CNPC_Hornet::IgniteTrail( void )
+{
+ Vector vColor;
+
+ if ( m_iHornetType == HORNET_TYPE_RED )
+ vColor = Vector ( 179, 39, 14 );
+ else
+ vColor = Vector ( 255, 128, 0 );
+
+ CBroadcastRecipientFilter filter;
+ TE_BeamFollow( filter, 0.0,
+ entindex(),
+ iHornetTrail,
+ 0,
+ 1,
+ 2,
+ 0.5,
+ 0.5,
+ vColor.x,
+ vColor.y,
+ vColor.z,
+ 128 );
+}
+
+
+unsigned int CNPC_Hornet::PhysicsSolidMaskForEntity( void ) const
+{
+ unsigned int iMask = BaseClass::PhysicsSolidMaskForEntity();
+
+ iMask &= ~CONTENTS_MONSTERCLIP;
+
+ return iMask;
+}
+
+//=========================================================
+// Tracking Hornet hit something
+//=========================================================
+void CNPC_Hornet::TrackTouch ( CBaseEntity *pOther )
+{
+ if ( !pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS) )
+ {
+ return;
+ }
+
+ if ( pOther == GetOwnerEntity() || pOther->GetModelIndex() == GetModelIndex() )
+ {// bumped into the guy that shot it.
+ //SetSolid( SOLID_NOT );
+ return;
+ }
+
+ int nRelationship = IRelationType( pOther );
+ if ( (nRelationship == D_FR || nRelationship == D_NU || nRelationship == D_LI) )
+ {
+ // hit something we don't want to hurt, so turn around.
+ Vector vecVel = GetAbsVelocity();
+
+ VectorNormalize( vecVel );
+
+ vecVel.x *= -1;
+ vecVel.y *= -1;
+
+ SetAbsOrigin( GetAbsOrigin() + vecVel * 4 ); // bounce the hornet off a bit.
+ SetAbsVelocity( vecVel * m_flFlySpeed );
+
+ return;
+ }
+
+ DieTouch( pOther );
+}
+
+void CNPC_Hornet::DartTouch( CBaseEntity *pOther )
+{
+ DieTouch( pOther );
+}
+
+//=========================================================
+// Hornet is flying, gently tracking target
+//=========================================================
+void CNPC_Hornet::TrackTarget ( void )
+{
+ Vector vecFlightDir;
+ Vector vecDirToEnemy;
+ float flDelta;
+
+ StudioFrameAdvance( );
+
+ if (gpGlobals->curtime > m_flStopAttack)
+ {
+ SetTouch( NULL );
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ return;
+ }
+
+ // UNDONE: The player pointer should come back after returning from another level
+ if ( GetEnemy() == NULL )
+ {// enemy is dead.
+ GetSenses()->Look( 1024 );
+ SetEnemy( BestEnemy() );
+ }
+
+ if ( GetEnemy() != NULL && FVisible( GetEnemy() ))
+ {
+ m_vecEnemyLKP = GetEnemy()->BodyTarget( GetAbsOrigin() );
+ }
+ else
+ {
+ m_vecEnemyLKP = m_vecEnemyLKP + GetAbsVelocity() * m_flFlySpeed * 0.1;
+ }
+
+ vecDirToEnemy = m_vecEnemyLKP - GetAbsOrigin();
+ VectorNormalize( vecDirToEnemy );
+
+ if ( GetAbsVelocity().Length() < 0.1 )
+ vecFlightDir = vecDirToEnemy;
+ else
+ {
+ vecFlightDir = GetAbsVelocity();
+ VectorNormalize( vecFlightDir );
+ }
+
+ SetAbsVelocity( vecFlightDir + vecDirToEnemy );
+
+ // measure how far the turn is, the wider the turn, the slow we'll go this time.
+ flDelta = DotProduct ( vecFlightDir, vecDirToEnemy );
+
+ if ( flDelta < 0.5 )
+ {// hafta turn wide again. play sound
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Hornet.Buzz" );
+ }
+
+ if ( flDelta <= 0 && m_iHornetType == HORNET_TYPE_RED )
+ {// no flying backwards, but we don't want to invert this, cause we'd go fast when we have to turn REAL far.
+ flDelta = 0.25;
+ }
+
+ Vector vecVel = vecFlightDir + vecDirToEnemy;
+ VectorNormalize( vecVel );
+
+ if ( GetOwnerEntity() && (GetOwnerEntity()->GetFlags() & FL_NPC) )
+ {
+ // random pattern only applies to hornets fired by monsters, not players.
+
+ vecVel.x += random->RandomFloat ( -0.10, 0.10 );// scramble the flight dir a bit.
+ vecVel.y += random->RandomFloat ( -0.10, 0.10 );
+ vecVel.z += random->RandomFloat ( -0.10, 0.10 );
+ }
+
+ switch ( m_iHornetType )
+ {
+ case HORNET_TYPE_RED:
+ SetAbsVelocity( vecVel * ( m_flFlySpeed * flDelta ) );// scale the dir by the ( speed * width of turn )
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.3 ) );
+ break;
+ default:
+ Assert( false ); //fall through if release
+ case HORNET_TYPE_ORANGE:
+ SetAbsVelocity( vecVel * m_flFlySpeed );// do not have to slow down to turn.
+ SetNextThink( gpGlobals->curtime + 0.1f );// fixed think time
+ break;
+ }
+
+ QAngle angNewAngles;
+ VectorAngles( GetAbsVelocity(), angNewAngles );
+ SetAbsAngles( angNewAngles );
+
+ SetSolid( SOLID_BBOX );
+
+ // if hornet is close to the enemy, jet in a straight line for a half second.
+ // (only in the single player game)
+ if ( GetEnemy() != NULL && !g_pGameRules->IsMultiplayer() )
+ {
+ if ( flDelta >= 0.4 && ( GetAbsOrigin() - m_vecEnemyLKP ).Length() <= 300 )
+ {
+ CPVSFilter filter( GetAbsOrigin() );
+ te->Sprite( filter, 0.0,
+ &GetAbsOrigin(), // pos
+ iHornetPuff, // model
+ 0.2, //size
+ 128 // brightness
+ );
+
+ CPASAttenuationFilter filter2( this );
+ EmitSound( filter2, entindex(), "Hornet.Buzz" );
+ SetAbsVelocity( GetAbsVelocity() * 2 );
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ // don't attack again
+ m_flStopAttack = gpGlobals->curtime;
+ }
+ }
+}
diff --git a/game/server/hl1/hl1_npc_hornet.h b/game/server/hl1/hl1_npc_hornet.h
new file mode 100644
index 0000000..0f8ccc6
--- /dev/null
+++ b/game/server/hl1/hl1_npc_hornet.h
@@ -0,0 +1,76 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_HORNET_H
+#define NPC_HORNET_H
+
+#include "hl1_ai_basenpc.h"
+
+//=========================================================
+// Hornets
+//=========================================================
+
+//=========================================================
+// Hornet Defines
+//=========================================================
+#define HORNET_TYPE_RED 0
+#define HORNET_TYPE_ORANGE 1
+#define HORNET_RED_SPEED (float)600
+#define HORNET_ORANGE_SPEED (float)800
+
+extern int iHornetPuff;
+
+//=========================================================
+// Hornet - this is the projectile that the Alien Grunt fires.
+//=========================================================
+class CNPC_Hornet : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Hornet, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ Class_T Classify ( void );
+ Disposition_t IRelationType(CBaseEntity *pTarget);
+
+ void DieTouch ( CBaseEntity *pOther );
+ void DartTouch( CBaseEntity *pOther );
+ void TrackTouch ( CBaseEntity *pOther );
+ void TrackTarget ( void );
+ void StartDart ( void );
+ void IgniteTrail( void );
+ void StartTrack(void);
+
+
+ virtual unsigned int PhysicsSolidMaskForEntity( void ) const;
+ virtual bool ShouldGib( const CTakeDamageInfo &info ) { return false; }
+
+ /* virtual int Save( CSave &save );
+ virtual int Restore( CRestore &restore );
+ static TYPEDESCRIPTION m_SaveData[];
+
+
+ void EXPORT StartTrack ( void );
+
+ void EXPORT TrackTarget ( void );
+ void EXPORT TrackTouch ( CBaseEntity *pOther );
+ void EXPORT DartTouch( CBaseEntity *pOther );
+
+
+ int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );*/
+
+ float m_flStopAttack;
+ int m_iHornetType;
+ float m_flFlySpeed;
+ int m_flDamage;
+
+ DECLARE_DATADESC();
+
+ Vector m_vecEnemyLKP;
+};
+
+#endif //NPC_HORNET_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_houndeye.cpp b/game/server/hl1/hl1_npc_houndeye.cpp
new file mode 100644
index 0000000..10ae189
--- /dev/null
+++ b/game/server/hl1/hl1_npc_houndeye.cpp
@@ -0,0 +1,1287 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Cute hound like Alien.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "game.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_navigator.h"
+#include "ai_route.h"
+#include "ai_squad.h"
+#include "ai_squadslot.h"
+#include "ai_hint.h"
+#include "npcevent.h"
+#include "animation.h"
+#include "hl1_npc_houndeye.h"
+#include "gib.h"
+#include "soundent.h"
+#include "ndebugoverlay.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "movevars_shared.h"
+
+// houndeye does 20 points of damage spread over a sphere 384 units in diameter, and each additional
+// squad member increases the BASE damage by 110%, per the spec.
+#define HOUNDEYE_MAX_SQUAD_SIZE 4
+#define HOUNDEYE_SQUAD_BONUS (float)1.1
+
+#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye
+
+#define HOUNDEYE_SOUND_STARTLE_VOLUME 128 // how loud a sound has to be to badly scare a sleeping houndeye
+
+#define HOUNDEYE_TOP_MASS 300.0f
+
+ConVar sk_houndeye_health ( "sk_houndeye_health", "20" );
+ConVar sk_houndeye_dmg_blast ( "sk_houndeye_dmg_blast", "15" );
+
+static int s_iSquadIndex = 0;
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define HOUND_AE_WARN 1
+#define HOUND_AE_STARTATTACK 2
+#define HOUND_AE_THUMP 3
+#define HOUND_AE_ANGERSOUND1 4
+#define HOUND_AE_ANGERSOUND2 5
+#define HOUND_AE_HOPBACK 6
+#define HOUND_AE_CLOSE_EYE 7
+
+BEGIN_DATADESC( CNPC_Houndeye )
+ DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fAsleep, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_fDontBlink, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vecPackCenter, FIELD_POSITION_VECTOR ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( monster_houndeye, CNPC_Houndeye );
+
+//=========================================================
+// monster-specific tasks
+//=========================================================
+enum
+{
+ TASK_HOUND_CLOSE_EYE = LAST_SHARED_TASK,
+ TASK_HOUND_OPEN_EYE,
+ TASK_HOUND_THREAT_DISPLAY,
+ TASK_HOUND_FALL_ASLEEP,
+ TASK_HOUND_WAKE_UP,
+ TASK_HOUND_HOP_BACK,
+};
+
+//=========================================================
+// monster-specific schedule types
+//=========================================================
+enum
+{
+ SCHED_HOUND_AGITATED = LAST_SHARED_SCHEDULE,
+ SCHED_HOUND_HOP_RETREAT,
+ SCHED_HOUND_YELL1,
+ SCHED_HOUND_YELL2,
+ SCHED_HOUND_RANGEATTACK,
+ SCHED_HOUND_SLEEP,
+ SCHED_HOUND_WAKE_LAZY,
+ SCHED_HOUND_WAKE_URGENT,
+ SCHED_HOUND_SPECIALATTACK,
+ SCHED_HOUND_COMBAT_FAIL_PVS,
+ SCHED_HOUND_COMBAT_FAIL_NOPVS,
+// SCHED_HOUND_FAIL,
+};
+
+enum HoundEyeSquadSlots
+{
+ SQUAD_SLOTS_HOUND_ATTACK = LAST_SHARED_SQUADSLOT,
+};
+
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Houndeye::Spawn()
+{
+ Precache( );
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ SetModel( "models/houndeye.mdl" );
+
+ SetHullType(HULL_TINY);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_YELLOW;
+ ClearEffects();
+ m_iHealth = sk_houndeye_health.GetFloat();
+ m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+ m_fAsleep = FALSE; // everyone spawns awake
+ m_fDontBlink = FALSE;
+
+ CapabilitiesClear();
+
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
+ CapabilitiesAdd( bits_CAP_SQUAD);
+
+ NPCInit();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Houndeye::Precache()
+{
+ PrecacheModel("models/houndeye.mdl");
+
+ m_iSpriteTexture = PrecacheModel( "sprites/shockwave.vmt" );
+
+ PrecacheScriptSound( "HoundEye.Idle" );
+ PrecacheScriptSound( "HoundEye.Warn" );
+ PrecacheScriptSound( "HoundEye.Hunt" );
+ PrecacheScriptSound( "HoundEye.Alert" );
+ PrecacheScriptSound( "HoundEye.Die" );
+ PrecacheScriptSound( "HoundEye.Pain" );
+ PrecacheScriptSound( "HoundEye.Anger1" );
+ PrecacheScriptSound( "HoundEye.Anger2" );
+ PrecacheScriptSound( "HoundEye.Sonic" );
+
+ BaseClass::Precache();
+}
+
+void CNPC_Houndeye::Event_Killed( const CTakeDamageInfo &info )
+{
+ // Close the eye to make death more obvious
+ m_nSkin = 1;
+ BaseClass::Event_Killed( info );
+}
+
+int CNPC_Houndeye::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ // I'm not allowed to attack if standing in another hound eye
+ // (note houndeyes allowed to interpenetrate)
+ trace_t tr;
+ UTIL_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,0.1),
+ GetHullMins(), GetHullMaxs(),
+ MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
+ if (tr.startsolid)
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if (pEntity->Classify() == CLASS_ALIEN_MONSTER)
+ {
+ return( COND_NONE );
+ }
+ }
+
+ // If I'm really close to my enemy allow me to attack if
+ // I'm facing regardless of next attack time
+ if (flDist < 100 && flDot >= 0.3)
+ {
+ return COND_CAN_RANGE_ATTACK1;
+ }
+ if ( gpGlobals->curtime < m_flNextAttack )
+ {
+ return( COND_NONE );
+ }
+ if (flDist > ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ))
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+ if (flDot < 0.3)
+ {
+ return COND_NOT_FACING_ATTACK;
+ }
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+//=========================================================
+// IdleSound
+//=========================================================
+void CNPC_Houndeye::IdleSound ( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HoundEye.Idle" );
+}
+
+//=========================================================
+// IdleSound
+//=========================================================
+void CNPC_Houndeye::WarmUpSound ( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(),"HoundEye.Warn" );
+}
+
+//=========================================================
+// WarnSound
+//=========================================================
+void CNPC_Houndeye::WarnSound ( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HoundEye.Hunt" );
+}
+
+//=========================================================
+// AlertSound
+//=========================================================
+void CNPC_Houndeye::AlertSound ( void )
+{
+
+ if ( m_pSquad && !m_pSquad->IsLeader( this ) )
+ return; // only leader makes ALERT sound.
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HoundEye.Alert" );
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_Houndeye::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HoundEye.Die" );
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_Houndeye::PainSound ( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HoundEye.Pain" );
+}
+
+//=========================================================
+// MaxYawSpeed - allows each sequence to have a different
+// turn rate associated with it.
+//=========================================================
+float CNPC_Houndeye::MaxYawSpeed ( void )
+{
+ int flYS;
+
+ flYS = 90;
+
+ switch ( GetActivity() )
+ {
+ case ACT_CROUCHIDLE://sleeping!
+ flYS = 0;
+ break;
+ case ACT_IDLE:
+ flYS = 60;
+ break;
+ case ACT_WALK:
+ flYS = 90;
+ break;
+ case ACT_RUN:
+ flYS = 90;
+ break;
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ flYS = 90;
+ break;
+ }
+
+ return flYS;
+}
+
+//=========================================================
+// Classify - indicates this monster's place in the
+// relationship table.
+//=========================================================
+Class_T CNPC_Houndeye::Classify ( void )
+{
+ return CLASS_ALIEN_MONSTER;
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Houndeye::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch ( pEvent->event )
+ {
+ case HOUND_AE_WARN:
+ // do stuff for this event.
+ WarnSound();
+ break;
+
+ case HOUND_AE_STARTATTACK:
+ WarmUpSound();
+ break;
+
+ case HOUND_AE_HOPBACK:
+ {
+ float flGravity = GetCurrentGravity();
+ Vector v_forward;
+ GetVectors( &v_forward, NULL, NULL );
+
+ SetGroundEntity( NULL );
+
+ Vector vecVel = v_forward * -200;
+ vecVel.z += ( 0.6 * flGravity ) * 0.5;
+ SetAbsVelocity( vecVel );
+
+ break;
+ }
+
+ case HOUND_AE_THUMP:
+ // emit the shockwaves
+ SonicAttack();
+ break;
+
+ case HOUND_AE_ANGERSOUND1:
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HoundEye.Anger1" );
+ }
+ break;
+
+ case HOUND_AE_ANGERSOUND2:
+ {
+ CPASAttenuationFilter filter2( this );
+ EmitSound( filter2, entindex(), "HoundEye.Anger2" );
+ }
+ break;
+
+ case HOUND_AE_CLOSE_EYE:
+ if ( !m_fDontBlink )
+ {
+ m_nSkin = HOUNDEYE_EYE_FRAMES - 1;
+ }
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+//=========================================================
+// SonicAttack
+//=========================================================
+void CNPC_Houndeye::SonicAttack ( void )
+{
+ float flAdjustedDamage;
+ float flDist;
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "HoundEye.Sonic");
+
+ CBroadcastRecipientFilter filter2;
+ te->BeamRingPoint( filter2, 0.0,
+ GetAbsOrigin(), //origin
+ 16, //start radius
+ HOUNDEYE_MAX_ATTACK_RADIUS,//end radius
+ m_iSpriteTexture, //texture
+ 0, //halo index
+ 0, //start frame
+ 0, //framerate
+ 0.2, //life
+ 24, //width
+ 16, //spread
+ 0, //amplitude
+ WriteBeamColor().x, //r
+ WriteBeamColor().y, //g
+ WriteBeamColor().z, //b
+ 192, //a
+ 0 //speed
+ );
+
+ CBroadcastRecipientFilter filter3;
+ te->BeamRingPoint( filter3, 0.0,
+ GetAbsOrigin(), //origin
+ 16, //start radius
+ HOUNDEYE_MAX_ATTACK_RADIUS / 2, //end radius
+ m_iSpriteTexture, //texture
+ 0, //halo index
+ 0, //start frame
+ 0, //framerate
+ 0.2, //life
+ 24, //width
+ 16, //spread
+ 0, //amplitude
+ WriteBeamColor().x, //r
+ WriteBeamColor().y, //g
+ WriteBeamColor().z, //b
+ 192, //a
+ 0 //speed
+ );
+
+ CBaseEntity *pEntity = NULL;
+ // iterate on all entities in the vicinity.
+ while ((pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL)
+ {
+ if ( pEntity->m_takedamage != DAMAGE_NO )
+ {
+ if ( !FClassnameIs(pEntity, "monster_houndeye") )
+ {// houndeyes don't hurt other houndeyes with their attack
+
+ // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise.
+ // This means that you must get out of the houndeye's attack range entirely to avoid damage.
+ // Calculate full damage first
+
+ if ( m_pSquad && m_pSquad->NumMembers() > 1 )
+ {
+ // squad gets attack bonus.
+ flAdjustedDamage = sk_houndeye_dmg_blast.GetFloat() + sk_houndeye_dmg_blast.GetFloat() * ( HOUNDEYE_SQUAD_BONUS * ( m_pSquad->NumMembers() - 1 ) );
+ }
+ else
+ {
+ // solo
+ flAdjustedDamage =sk_houndeye_dmg_blast.GetFloat();
+ }
+
+ flDist = (pEntity->WorldSpaceCenter() - GetAbsOrigin()).Length();
+
+ flAdjustedDamage -= ( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ) * flAdjustedDamage;
+
+ if ( !FVisible( pEntity ) )
+ {
+ if ( pEntity->IsPlayer() )
+ {
+ // if this entity is a client, and is not in full view, inflict half damage. We do this so that players still
+ // take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients
+ // so that monsters in other parts of the level don't take the damage and get pissed.
+ flAdjustedDamage *= 0.5;
+ }
+ else if ( !FClassnameIs( pEntity, "func_breakable" ) && !FClassnameIs( pEntity, "func_pushable" ) )
+ {
+ // do not hurt nonclients through walls, but allow damage to be done to breakables
+ flAdjustedDamage = 0;
+ }
+ }
+
+ //ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage );
+
+ if (flAdjustedDamage > 0 )
+ {
+ CTakeDamageInfo info( this, this, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB );
+ CalculateExplosiveDamageForce( &info, (pEntity->GetAbsOrigin() - GetAbsOrigin()), pEntity->GetAbsOrigin() );
+
+ pEntity->TakeDamage( info );
+
+ if ( (pEntity->GetAbsOrigin() - GetAbsOrigin()).Length2D() <= HOUNDEYE_MAX_ATTACK_RADIUS )
+ {
+ if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() && !pEntity->IsPlayer()) )
+ {
+ IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
+
+ if ( pPhysObject )
+ {
+ float flMass = pPhysObject->GetMass();
+
+ if ( flMass <= HOUNDEYE_TOP_MASS )
+ {
+ // Increase the vertical lift of the force
+ Vector vecForce = info.GetDamageForce();
+ vecForce.z *= 2.0f;
+ info.SetDamageForce( vecForce );
+
+ pEntity->VPhysicsTakeDamage( info );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+//=========================================================
+// WriteBeamColor - writes a color vector to the network
+// based on the size of the group.
+//=========================================================
+Vector CNPC_Houndeye::WriteBeamColor ( void )
+{
+ BYTE bRed, bGreen, bBlue;
+
+ if ( m_pSquad )
+ {
+ switch ( m_pSquad->NumMembers() )
+ {
+ case 1:
+ // solo houndeye - weakest beam
+ bRed = 188;
+ bGreen = 220;
+ bBlue = 255;
+ break;
+ case 2:
+ bRed = 101;
+ bGreen = 133;
+ bBlue = 221;
+ break;
+ case 3:
+ bRed = 67;
+ bGreen = 85;
+ bBlue = 255;
+ break;
+ case 4:
+ bRed = 62;
+ bGreen = 33;
+ bBlue = 211;
+ break;
+ default:
+ Msg ( "Unsupported Houndeye SquadSize!\n" );
+ bRed = 188;
+ bGreen = 220;
+ bBlue = 255;
+ break;
+ }
+ }
+ else
+ {
+ // solo houndeye - weakest beam
+ bRed = 188;
+ bGreen = 220;
+ bBlue = 255;
+ }
+
+
+ return Vector ( bRed, bGreen, bBlue );
+}
+
+bool CNPC_Houndeye::ShouldGoToIdleState( void )
+{
+ if ( m_pSquad )
+ {
+ AISquadIter_t iter;
+ for (CAI_BaseNPC *pMember = m_pSquad->GetFirstMember( &iter ); pMember; pMember = m_pSquad->GetNextMember( &iter ) )
+ {
+ if ( pMember != this && pMember->GetHintNode() && pMember->GetHintNode()->HintType() != NO_NODE )
+ return true;
+ }
+
+ return true;
+ }
+
+ return true;
+}
+
+bool CNPC_Houndeye::FValidateHintType ( CAI_Hint *pHint )
+{
+ switch( pHint->HintType() )
+ {
+ case HINT_HL1_WORLD_MACHINERY:
+ return true;
+ break;
+ case HINT_HL1_WORLD_BLINKING_LIGHT:
+ return true;
+ break;
+ case HINT_HL1_WORLD_HUMAN_BLOOD:
+ return true;
+ break;
+ case HINT_HL1_WORLD_ALIEN_BLOOD:
+ return true;
+ break;
+ }
+
+ Msg ( "Couldn't validate hint type" );
+
+ return false;
+}
+
+//=========================================================
+// SetActivity
+//=========================================================
+void CNPC_Houndeye::SetActivity ( Activity NewActivity )
+{
+ int iSequence;
+
+ if ( NewActivity == GetActivity() )
+ return;
+
+ if ( m_NPCState == NPC_STATE_COMBAT && NewActivity == ACT_IDLE && random->RandomInt( 0, 1 ) )
+ {
+ // play pissed idle.
+ iSequence = LookupSequence( "madidle" );
+
+ SetActivity( NewActivity ); // Go ahead and set this so it doesn't keep trying when the anim is not present
+
+ // In case someone calls this with something other than the ideal activity
+ SetIdealActivity( GetActivity() );
+
+ // Set to the desired anim, or default anim if the desired is not present
+ if ( iSequence > ACTIVITY_NOT_AVAILABLE )
+ {
+ SetSequence( iSequence ); // Set to the reset anim (if it's there)
+ SetCycle( 0 ); // FIX: frame counter shouldn't be reset when its the same activity as before
+ ResetSequenceInfo();
+ }
+ }
+ else
+ {
+ BaseClass::SetActivity ( NewActivity );
+ }
+}
+
+//=========================================================
+// start task
+//=========================================================
+void CNPC_Houndeye::StartTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HOUND_FALL_ASLEEP:
+ {
+ m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!)
+ TaskComplete();
+ break;
+ }
+ case TASK_HOUND_WAKE_UP:
+ {
+ m_fAsleep = FALSE; // signal that hound is standing again
+ TaskComplete();
+ break;
+ }
+ case TASK_HOUND_OPEN_EYE:
+ {
+ m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye
+ TaskComplete();
+ break;
+ }
+ case TASK_HOUND_CLOSE_EYE:
+ {
+ m_nSkin = 0;
+ m_fDontBlink = TRUE; // tell blink code to leave the eye alone.
+ break;
+ }
+ case TASK_HOUND_THREAT_DISPLAY:
+ {
+ SetIdealActivity( ACT_IDLE_ANGRY );
+ break;
+ }
+ case TASK_HOUND_HOP_BACK:
+ {
+ SetIdealActivity( ACT_LEAP );
+ break;
+ }
+ case TASK_RANGE_ATTACK1:
+ {
+ SetIdealActivity( ACT_RANGE_ATTACK1 );
+ break;
+ }
+ case TASK_SPECIAL_ATTACK1:
+ {
+ SetIdealActivity( ACT_SPECIAL_ATTACK1 );
+ break;
+ }
+ default:
+ {
+ BaseClass::StartTask(pTask);
+ break;
+ }
+ }
+}
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_Houndeye::RunTask ( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HOUND_THREAT_DISPLAY:
+ {
+ if ( GetEnemy() )
+ {
+ float idealYaw;
+ idealYaw = UTIL_VecToYaw( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ GetMotor()->SetIdealYawAndUpdate( idealYaw );
+ }
+
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete();
+ }
+
+ break;
+ }
+ case TASK_HOUND_CLOSE_EYE:
+ {
+ if ( m_nSkin < HOUNDEYE_EYE_FRAMES - 1 )
+ m_nSkin++;
+ break;
+ }
+ case TASK_HOUND_HOP_BACK:
+ {
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete();
+ }
+ break;
+ }
+ case TASK_SPECIAL_ATTACK1:
+ {
+ m_nSkin = random->RandomInt(0, HOUNDEYE_EYE_FRAMES - 1);
+
+ float idealYaw;
+ idealYaw = UTIL_VecToYaw( GetNavigator()->GetGoalPos() );
+ GetMotor()->SetIdealYawAndUpdate( idealYaw );
+
+ float life;
+ life = ((255 - GetCycle()) / ( m_flPlaybackRate * m_flPlaybackRate));
+ if (life < 0.1)
+ {
+ life = 0.1;
+ }
+
+ /* MessageBegin( MSG_PAS, SVC_TEMPENTITY, GetAbsOrigin() );
+ WRITE_BYTE( TE_IMPLOSION);
+ WRITE_COORD( GetAbsOrigin().x);
+ WRITE_COORD( GetAbsOrigin().y);
+ WRITE_COORD( GetAbsOrigin().z + 16);
+ WRITE_BYTE( 50 * life + 100); // radius
+ WRITE_BYTE( pev->frame / 25.0 ); // count
+ WRITE_BYTE( life * 10 ); // life
+ MessageEnd();*/
+
+ if ( IsSequenceFinished() )
+ {
+ SonicAttack();
+ TaskComplete();
+ }
+
+ break;
+ }
+ default:
+ {
+ BaseClass::RunTask(pTask);
+ break;
+ }
+ }
+}
+
+//=========================================================
+// PrescheduleThink
+//=========================================================
+void CNPC_Houndeye::PrescheduleThink ( void )
+{
+ BaseClass::PrescheduleThink();
+
+ // if the hound is mad and is running, make hunt noises.
+ if ( m_NPCState == NPC_STATE_COMBAT && GetActivity() == ACT_RUN && random->RandomFloat( 0, 1 ) < 0.2 )
+ {
+ WarnSound();
+ }
+
+ // at random, initiate a blink if not already blinking or sleeping
+ if ( !m_fDontBlink )
+ {
+ if ( ( m_nSkin == 0 ) && random->RandomInt(0,0x7F) == 0 )
+ {// start blinking!
+ m_nSkin = HOUNDEYE_EYE_FRAMES - 1;
+ }
+ else if ( m_nSkin != 0 )
+ {// already blinking
+ m_nSkin--;
+ }
+ }
+
+ // if you are the leader, average the origins of each pack member to get an approximate center.
+ if ( m_pSquad && m_pSquad->IsLeader( this ) )
+ {
+ int iSquadCount = 0;
+
+ AISquadIter_t iter;
+ for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
+ {
+ iSquadCount++;
+ m_vecPackCenter = m_vecPackCenter + pSquadMember->GetAbsOrigin();
+ }
+
+ m_vecPackCenter = m_vecPackCenter / iSquadCount;
+ }
+}
+
+//=========================================================
+// GetScheduleOfType
+//=========================================================
+int CNPC_Houndeye::TranslateSchedule( int scheduleType )
+{
+ if ( m_fAsleep )
+ {
+ // if the hound is sleeping, must wake and stand!
+ if ( HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_THUMPER ) || HasCondition( COND_HEAR_COMBAT ) ||
+ HasCondition( COND_HEAR_WORLD ) || HasCondition( COND_HEAR_PLAYER ) || HasCondition( COND_HEAR_BULLET_IMPACT ) )
+ {
+ CSound *pWakeSound;
+
+ pWakeSound = GetBestSound();
+ ASSERT( pWakeSound != NULL );
+ if ( pWakeSound )
+ {
+ GetMotor()->SetIdealYawToTarget ( pWakeSound->GetSoundOrigin() );
+
+ if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME )
+ {
+ // awakened by a loud sound
+ return SCHED_HOUND_WAKE_URGENT;
+ }
+ }
+ // sound was not loud enough to scare the bejesus out of houndeye
+ return SCHED_HOUND_WAKE_LAZY;
+ }
+ else if ( HasCondition( COND_NEW_ENEMY ) )
+ {
+ // get up fast, to fight.
+ return SCHED_HOUND_WAKE_URGENT;
+ }
+
+ else
+ {
+ // hound is waking up on its own
+ return SCHED_HOUND_WAKE_LAZY;
+ }
+ }
+ switch ( scheduleType )
+ {
+ case SCHED_IDLE_STAND:
+ {
+ // we may want to sleep instead of stand!
+ if ( m_pSquad && !m_pSquad->IsLeader( this ) && !m_fAsleep && random->RandomInt( 0,29 ) < 1 )
+ {
+ return SCHED_HOUND_SLEEP;
+ }
+ else
+ {
+ return BaseClass::TranslateSchedule( scheduleType );
+ }
+ }
+
+ case SCHED_RANGE_ATTACK1:
+ return SCHED_HOUND_RANGEATTACK;
+ case SCHED_SPECIAL_ATTACK1:
+ return SCHED_HOUND_SPECIALATTACK;
+
+ case SCHED_FAIL:
+ {
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ {
+ if ( !FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
+ {
+ // client in PVS
+ return SCHED_HOUND_COMBAT_FAIL_PVS;
+ }
+ else
+ {
+ // client has taken off!
+ return SCHED_HOUND_COMBAT_FAIL_NOPVS;
+ }
+ }
+ else
+ {
+ return BaseClass::TranslateSchedule ( scheduleType );
+ }
+ }
+ default:
+ {
+ return BaseClass::TranslateSchedule ( scheduleType );
+ }
+ }
+}
+
+int CNPC_Houndeye::SelectSchedule( void )
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ {
+// dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // call base class, all code to handle dead enemies is centralized there.
+ return BaseClass::SelectSchedule();
+ }
+
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ if ( random->RandomFloat( 0 , 1 ) <= 0.4 )
+ {
+ trace_t trace;
+ Vector v_forward;
+ GetVectors( &v_forward, NULL, NULL );
+ UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + v_forward * -128, MASK_SOLID, &trace );
+
+ if ( trace.fraction == 1.0 )
+ {
+ // it's clear behind, so the hound will jump
+ return SCHED_HOUND_HOP_RETREAT;
+ }
+ }
+
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ // don't constraint attacks based on squad slots, just let em all go for it
+// if ( OccupyStrategySlot ( SQUAD_SLOTS_HOUND_ATTACK ) )
+ return SCHED_RANGE_ATTACK1;
+ }
+ break;
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+
+//=========================================================
+// FLSoundVolume - subtracts the volume of the given sound
+// from the distance the sound source is from the caller,
+// and returns that value, which is considered to be the 'local'
+// volume of the sound.
+//=========================================================
+float CNPC_Houndeye::FLSoundVolume( CSound *pSound )
+{
+ return ( pSound->Volume() - ( ( pSound->GetSoundOrigin() - GetAbsOrigin() ).Length() ) );
+}
+
+
+//=========================================================
+//
+// SquadRecruit(), get some monsters of my classification and
+// link them as a group. returns the group size
+//
+//=========================================================
+int CNPC_Houndeye::SquadRecruit( int searchRadius, int maxMembers )
+{
+ int squadCount;
+ int iMyClass = Classify();// cache this monster's class
+
+ if ( maxMembers < 2 )
+ return 0;
+
+ // I am my own leader
+ squadCount = 1;
+
+ CBaseEntity *pEntity = NULL;
+
+ if ( m_SquadName != NULL_STRING )
+ {
+ // I have a netname, so unconditionally recruit everyone else with that name.
+ pEntity = gEntList.FindEntityByClassname( pEntity, "monster_houndeye" );
+
+ while ( pEntity && squadCount < maxMembers )
+ {
+ CNPC_Houndeye *pRecruit = (CNPC_Houndeye*)pEntity->MyNPCPointer();
+
+ if ( pRecruit )
+ {
+ if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass && pRecruit != this )
+ {
+ // minimum protection here against user error.in worldcraft.
+ if ( pRecruit->m_SquadName != NULL_STRING && FStrEq( STRING( m_SquadName ), STRING( pRecruit->m_SquadName ) ) )
+ {
+ pRecruit->InitSquad();
+ squadCount++;
+ }
+ }
+ }
+
+ pEntity = gEntList.FindEntityByClassname( pEntity, "monster_houndeye" );
+ }
+
+ return squadCount;
+ }
+ else
+ {
+ char szSquadName[64];
+ Q_snprintf( szSquadName, sizeof( szSquadName ), "squad%d\n", s_iSquadIndex );
+
+ m_SquadName = MAKE_STRING( szSquadName );
+
+ while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), searchRadius ) ) != NULL && squadCount < maxMembers )
+ {
+ if ( !FClassnameIs ( pEntity, "monster_houndeye" ) )
+ continue;
+
+ CNPC_Houndeye *pRecruit = (CNPC_Houndeye*)pEntity->MyNPCPointer();
+
+ if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_hCine )
+ {
+ // Can we recruit this guy?
+ if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass &&
+ ( (iMyClass != CLASS_ALIEN_MONSTER) || FClassnameIs( this, pRecruit->GetClassname() ) ) &&
+ !pRecruit->m_SquadName )
+ {
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin() + GetViewOffset(), pRecruit->GetAbsOrigin() + GetViewOffset(), MASK_NPCSOLID_BRUSHONLY, pRecruit, COLLISION_GROUP_NONE, &tr );// try to hit recruit with a traceline.
+
+ if ( tr.fraction == 1.0 )
+ {
+ //We're ready to recruit people, so start a squad if I don't have one.
+ if ( !m_pSquad )
+ {
+ InitSquad();
+ }
+
+ pRecruit->m_SquadName = m_SquadName;
+
+ pRecruit->CapabilitiesAdd ( bits_CAP_SQUAD );
+ pRecruit->InitSquad();
+
+ squadCount++;
+ }
+ }
+ }
+ }
+
+ if ( squadCount > 1 )
+ {
+ s_iSquadIndex++;
+ }
+ }
+
+ return squadCount;
+}
+
+
+
+void CNPC_Houndeye::StartNPC ( void )
+{
+ if ( !m_pSquad )
+ {
+ if ( m_SquadName != NULL_STRING )
+ {
+ BaseClass::StartNPC();
+ return;
+ }
+ else
+ {
+ int iSquadSize = SquadRecruit( 1024, 4 );
+
+ if ( iSquadSize )
+ {
+ Msg ( "Squad of %d %s formed\n", iSquadSize, GetClassname() );
+ }
+ }
+ }
+
+ BaseClass::StartNPC();
+}
+
+
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( monster_houndeye, CNPC_Houndeye )
+
+ DECLARE_TASK ( TASK_HOUND_CLOSE_EYE )
+ DECLARE_TASK ( TASK_HOUND_OPEN_EYE )
+ DECLARE_TASK ( TASK_HOUND_THREAT_DISPLAY )
+ DECLARE_TASK ( TASK_HOUND_FALL_ASLEEP )
+ DECLARE_TASK ( TASK_HOUND_WAKE_UP )
+ DECLARE_TASK ( TASK_HOUND_HOP_BACK )
+
+ //=========================================================
+ // > SCHED_HOUND_RANGEATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_RANGEATTACK,
+
+ " Tasks"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_YELL1"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_AGITATED
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_AGITATED,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HOUND_THREAT_DISPLAY 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_HOP_RETREAT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_HOP_RETREAT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HOUND_HOP_BACK 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_YELL1
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_YELL1,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_RANGE_ATTACK1 0"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_AGITATED"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_YELL2
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_YELL2,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ )
+
+
+ //=========================================================
+ // > SCHED_HOUND_SLEEP
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_SLEEP,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_RANDOM 5"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCH"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_HOUND_FALL_ASLEEP 0"
+ " TASK_WAIT_RANDOM 25"
+ " TASK_HOUND_CLOSE_EYE 0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_COMBAT"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_PLAYER"
+ " COND_HEAR_WORLD"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_WAKE_LAZY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_WAKE_LAZY,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HOUND_OPEN_EYE 0"
+ " TASK_WAIT_RANDOM 2.5"
+ " TASK_PLAY_SEQUENCE ACT_STAND"
+ " TASK_HOUND_WAKE_UP 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_WAKE_URGENT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_WAKE_URGENT,
+
+ " Tasks"
+ " TASK_HOUND_OPEN_EYE 0"
+ " TASK_PLAY_SEQUENCE ACT_HOP"
+ " TASK_FACE_IDEAL 0"
+ " TASK_HOUND_WAKE_UP 0"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_SPECIALATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_SPECIALATTACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_SPECIAL_ATTACK1 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_IDLE_ANGRY"
+ " "
+ " Interrupts"
+ " "
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_ENEMY_OCCLUDED"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_COMBAT_FAIL_PVS
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_COMBAT_FAIL_PVS,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HOUND_THREAT_DISPLAY 0"
+ " TASK_WAIT_FACE_ENEMY 1"
+ " "
+ " Interrupts"
+ " "
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+ //=========================================================
+ // > SCHED_HOUND_COMBAT_FAIL_NOPVS
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HOUND_COMBAT_FAIL_NOPVS,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HOUND_THREAT_DISPLAY 0"
+ " TASK_WAIT_FACE_ENEMY 1"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT_PVS 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ )
+
+AI_END_CUSTOM_NPC()
diff --git a/game/server/hl1/hl1_npc_houndeye.h b/game/server/hl1/hl1_npc_houndeye.h
new file mode 100644
index 0000000..dfe7495
--- /dev/null
+++ b/game/server/hl1/hl1_npc_houndeye.h
@@ -0,0 +1,73 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_HOUNDEYE_H
+#define NPC_HOUNDEYE_H
+#pragma once
+
+#include "hl1_ai_basenpc.h"
+#define HOUNDEYE_MAX_ATTACK_RADIUS 384
+
+class CNPC_Houndeye : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Houndeye, CHL1BaseNPC );
+
+public:
+ void Spawn( void );
+ void Precache( void );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ void WarmUpSound ( void );
+ void AlertSound( void );
+ void DeathSound( const CTakeDamageInfo &info );
+ void WarnSound( void );
+ void PainSound( const CTakeDamageInfo &info );
+ void IdleSound( void );
+
+ float MaxYawSpeed ( void );
+
+ Class_T Classify ( void );
+
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ void SonicAttack ( void );
+
+ Vector WriteBeamColor( void );
+ bool ShouldGoToIdleState( void );
+ bool FValidateHintType ( CAI_Hint *pHint );
+
+ void SetActivity ( Activity NewActivity );
+
+ void StartTask( const Task_t *pTask );
+ void RunTask ( const Task_t *pTask );
+ void PrescheduleThink ( void );
+
+ int TranslateSchedule( int scheduleType );
+ int SelectSchedule( void );
+
+ float FLSoundVolume( CSound *pSound );
+ int RangeAttack1Conditions ( float flDot, float flDist );
+
+ void StartNPC ( void );
+
+ virtual float InnateRange1MinRange( void ) { return 0.0f; }
+ virtual float InnateRange1MaxRange( void ) { return HOUNDEYE_MAX_ATTACK_RADIUS; }
+
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+
+private:
+ int SquadRecruit( int searchRadius, int maxMembers );
+
+ int m_iSpriteTexture;
+ bool m_fAsleep;// some houndeyes sleep in idle mode if this is set, the houndeye is lying down
+ bool m_fDontBlink;// don't try to open/close eye if this bit is set!
+ Vector m_vecPackCenter; // the center of the pack. The leader maintains this by averaging the origins of all pack members.
+};
+
+#endif // NPC_HOUNDEYE_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_ichthyosaur.cpp b/game/server/hl1/hl1_npc_ichthyosaur.cpp
new file mode 100644
index 0000000..1e17a2d
--- /dev/null
+++ b/game/server/hl1/hl1_npc_ichthyosaur.cpp
@@ -0,0 +1,1024 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Ichthyosaur - buh bum... buh bum...
+//
+// $NoKeywords: $
+//=============================================================================//
+#include "cbase.h"
+#include "beam_shared.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 "activitylist.h"
+#include "game.h"
+#include "npcevent.h"
+#include "player.h"
+#include "entitylist.h"
+#include "soundenvelope.h"
+#include "shake.h"
+#include "ndebugoverlay.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "hl1_npc_ichthyosaur.h"
+
+ConVar sk_ichthyosaur_health ( "sk_ichthyosaur_health", "0" );
+ConVar sk_ichthyosaur_shake ( "sk_ichthyosaur_shake", "0" );
+
+#define ICH_SWIM_SPEED_WALK 150
+#define ICH_SWIM_SPEED_RUN 400
+#define PROBE_LENGTH 150
+
+enum IchthyosaurMoveType_t
+{
+ ICH_MOVETYPE_SEEK = 0, // Fly through the target without stopping.
+ ICH_MOVETYPE_ARRIVE // Slow down and stop at target.
+};
+
+enum
+{
+ SCHED_SWIM_AROUND = LAST_SHARED_SCHEDULE + 1,
+ SCHED_SWIM_AGITATED,
+ SCHED_CIRCLE_ENEMY,
+ SCHED_TWITCH_DIE,
+};
+
+
+//=========================================================
+// monster-specific tasks and states
+//=========================================================
+enum
+{
+ TASK_ICHTHYOSAUR_CIRCLE_ENEMY = LAST_SHARED_TASK + 1,
+ TASK_ICHTHYOSAUR_SWIM,
+ TASK_ICHTHYOSAUR_FLOAT,
+};
+
+
+LINK_ENTITY_TO_CLASS( monster_ichthyosaur, CNPC_Ichthyosaur );
+
+BEGIN_DATADESC( CNPC_Ichthyosaur )
+
+ // Function Pointers
+ DEFINE_ENTITYFUNC( BiteTouch ),
+
+ DEFINE_FIELD( m_SaveVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_idealDist, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flBlink, FIELD_TIME ),
+ DEFINE_FIELD( m_flEnemyTouched, FIELD_TIME ),
+ DEFINE_FIELD( m_bOnAttack, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flMinSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flMaxDist, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextAlert, FIELD_TIME ),
+ DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecLastMoveTarget, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bHasMoveTarget, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flFlyingSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLastAttackSound, FIELD_TIME ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartCombat", InputStartCombat ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "EndCombat", InputEndCombat ),
+
+END_DATADESC()
+
+//=========================================================
+// AI Schedules Specific to this monster
+//=========================================================
+
+
+AI_BEGIN_CUSTOM_NPC( monster_ichthyosaur, CNPC_Ichthyosaur )
+
+DECLARE_TASK ( TASK_ICHTHYOSAUR_SWIM )
+DECLARE_TASK ( TASK_ICHTHYOSAUR_CIRCLE_ENEMY )
+DECLARE_TASK ( TASK_ICHTHYOSAUR_FLOAT )
+
+ //=========================================================
+ // > SCHED_SWIM_AROUND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SWIM_AROUND,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_GLIDE"
+ " TASK_ICHTHYOSAUR_SWIM 0.0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_SEE_ENEMY"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_PLAYER"
+ " COND_HEAR_COMBAT"
+ )
+ //=========================================================
+ // > SCHED_SWIM_AGITATED
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SWIM_AGITATED,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_SWIM"
+ " TASK_WAIT 2.0"
+ " "
+ )
+ //=========================================================
+ // > SCHED_CIRCLE_ENEMY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CIRCLE_ENEMY,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_GLIDE"
+ " TASK_ICHTHYOSAUR_CIRCLE_ENEMY 0.0"
+ " "
+ " Interrupts"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_NEW_ENEMY"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_CAN_RANGE_ATTACK1"
+ )
+ //=========================================================
+ // > SCHED_TWITCH_DIE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_TWITCH_DIE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SOUND_DIE 0"
+ " TASK_DIE 0"
+ " TASK_ICHTHYOSAUR_FLOAT 0"
+ " "
+ )
+
+AI_END_CUSTOM_NPC()
+
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Ichthyosaur::Precache()
+{
+ PrecacheModel("models/icky.mdl");
+
+ PrecacheModel("sprites/lgtning.vmt");
+
+ PrecacheScriptSound( "Ichthyosaur.Bite" );
+ PrecacheScriptSound( "Ichthyosaur.Alert" );
+ PrecacheScriptSound( "Ichthyosaur.Pain" );
+ PrecacheScriptSound( "Ichthyosaur.Die" );
+ PrecacheScriptSound( "Ichthyosaur.Idle" );
+ PrecacheScriptSound( "Ichthyosaur.Attack" );
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Ichthyosaur::Spawn( void )
+{
+ Precache( );
+
+ SetModel( "models/icky.mdl");
+ UTIL_SetSize( this, Vector( -32, -32, -32 ), Vector( 32, 32, 32 ) );
+
+ SetHullType(HULL_LARGE_CENTERED);
+ SetHullSizeNormal();
+ SetDefaultEyeOffset();
+
+ // Use our hitboxes to determine our render bounds
+ CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES );
+
+ SetNavType( NAV_FLY );
+ m_NPCState = NPC_STATE_NONE;
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ AddFlag( FL_FLY | FL_STEPMOVEMENT );
+
+ m_flGroundSpeed = ICH_SWIM_SPEED_RUN;
+
+ m_bloodColor = BLOOD_COLOR_YELLOW;
+ m_iHealth = sk_ichthyosaur_health.GetFloat();
+ m_iMaxHealth = m_iHealth;
+ m_flFieldOfView = -0.707; // 270 degrees
+
+ AddFlag( FL_SWIM );
+
+ m_flFlyingSpeed = ICHTHYOSAUR_SPEED;
+
+ SetDistLook( 1024 );
+
+
+ SetTouch( &CNPC_Ichthyosaur::BiteTouch );
+
+ m_idealDist = 384;
+ m_flMinSpeed = 80;
+ m_flMaxSpeed = 400;
+ m_flMaxDist = 384;
+ m_flLastAttackSound = gpGlobals->curtime;
+
+ Vector vforward;
+ AngleVectors(GetAbsAngles(), &vforward );
+ VectorNormalize ( vforward );
+ SetAbsVelocity( m_flFlyingSpeed * vforward );
+ m_SaveVelocity = GetAbsVelocity();
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK1 );
+
+ m_bOnAttack = false;
+
+ NPCInit();
+}
+
+
+//=========================================================
+//=========================================================
+int CNPC_Ichthyosaur::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ case SCHED_IDLE_WALK:
+ return SCHED_SWIM_AROUND;
+ case SCHED_STANDOFF:
+ return SCHED_CIRCLE_ENEMY;
+ case SCHED_FAIL:
+ return SCHED_SWIM_AGITATED;
+ case SCHED_DIE:
+ return SCHED_TWITCH_DIE;
+ case SCHED_CHASE_ENEMY:
+
+ if ( m_flLastAttackSound < gpGlobals->curtime )
+ {
+ AttackSound();
+ m_flLastAttackSound = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f );
+ }
+
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+int CNPC_Ichthyosaur::SelectSchedule()
+{
+ switch(m_NPCState)
+ {
+ case NPC_STATE_IDLE:
+ m_flFlyingSpeed = 80;
+ m_flMaxSpeed = 80;
+ return TranslateSchedule( SCHED_IDLE_WALK );
+
+ case NPC_STATE_ALERT:
+ m_flFlyingSpeed = 150;
+ m_flMaxSpeed = 150;
+ return TranslateSchedule( SCHED_IDLE_WALK );
+
+ case NPC_STATE_COMBAT:
+ m_flMaxSpeed = 400;
+
+ // eat them
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return TranslateSchedule( SCHED_MELEE_ATTACK1 );
+ }
+
+ // chase them down and eat them
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ {
+ return TranslateSchedule( SCHED_CHASE_ENEMY );
+ }
+
+ if ( HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ m_bOnAttack = true;
+ }
+
+ if ( GetHealth() < GetMaxHealth() - 20 )
+ {
+ m_bOnAttack = true;
+ }
+
+ return TranslateSchedule( SCHED_STANDOFF );
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+
+bool CNPC_Ichthyosaur::OverrideMove( float flInterval )
+{
+ if ( m_lifeState == LIFE_ALIVE )
+ {
+ if ( m_NPCState != NPC_STATE_SCRIPT)
+ {
+ MoveExecute_Alive( flInterval );
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Ichthyosaur::MoveExecute_Alive(float flInterval)
+{
+ Vector vStart = GetAbsOrigin();
+ Vector vForward, vRight, vUp;
+
+ if (GetNavigator()->IsGoalActive())
+ {
+ Vector vecDir = ( GetNavigator()->GetPath()->CurWaypointPos() - GetAbsOrigin());
+ VectorNormalize( vecDir );
+
+ m_SaveVelocity = vecDir * m_flFlyingSpeed;
+ }
+
+ // If we're attacking, accelerate to max speed
+ if (m_bOnAttack && m_flFlyingSpeed < m_flMaxSpeed)
+ {
+ m_flFlyingSpeed = MIN( m_flMaxSpeed, m_flFlyingSpeed+40 );
+ }
+
+ if (m_flFlyingSpeed < 180)
+ {
+ if (GetIdealActivity() == ACT_SWIM)
+ SetActivity( ACT_GLIDE );
+ if (GetIdealActivity() == ACT_GLIDE)
+ m_flPlaybackRate = m_flFlyingSpeed / 150.0;
+ }
+ else
+ {
+ if (GetIdealActivity() == ACT_GLIDE)
+ SetActivity( ACT_SWIM );
+ if (GetIdealActivity() == ACT_SWIM)
+ m_flPlaybackRate = m_flFlyingSpeed / 300.0;
+ }
+
+ // Steering
+ QAngle angSaveAngles;
+ VectorAngles( m_SaveVelocity, angSaveAngles );
+ AngleVectors(angSaveAngles, &vForward, &vRight, &vUp);
+
+ Vector z;
+ float frac;
+
+ Vector f, u, l, r, d;
+ f = DoProbe(vStart + (PROBE_LENGTH * vForward) );
+ r = DoProbe(vStart + ((PROBE_LENGTH/3) * (vForward + vRight)) );
+ l = DoProbe(vStart + ((PROBE_LENGTH/3) * (vForward - vRight)) );
+ u = DoProbe(vStart + ((PROBE_LENGTH/3) * (vForward + vUp)) );
+ d = DoProbe(vStart + ((PROBE_LENGTH/3) * (vForward - vUp)) );
+
+ Vector SteeringVector = f+r+l+u+d;
+
+ if( ProbeZ( vStart + vForward*50, vUp*50, &frac ) )
+ {
+ // reflect off the water surface
+ m_SaveVelocity.z = -10;
+ }
+
+ m_SaveVelocity += SteeringVector/32;
+ VectorNormalize( m_SaveVelocity );
+
+ angSaveAngles = GetAbsAngles();
+
+ AngleVectors( angSaveAngles, &vForward, &vRight, &vUp );
+
+ float flDot = DotProduct( vForward, m_SaveVelocity );
+ if (flDot > 0.5)
+ m_SaveVelocity = m_SaveVelocity * m_flFlyingSpeed;
+ else if (flDot > 0)
+ m_SaveVelocity = m_SaveVelocity * m_flFlyingSpeed * (flDot + 0.5);
+ else
+ m_SaveVelocity = m_SaveVelocity * 80;
+
+ SetAbsVelocity( m_SaveVelocity );
+
+ VectorAngles( m_SaveVelocity, angSaveAngles );
+
+ //
+ // Smooth Pitch
+ //
+ if (angSaveAngles.x > 180)
+ angSaveAngles.x = angSaveAngles.x - 360;
+
+ QAngle angAbsAngles = GetAbsAngles();
+
+ angAbsAngles.x = clamp( UTIL_Approach(angSaveAngles.x, angAbsAngles.x, 10 ), -60, 60 );
+
+ //
+ // Smooth Yaw and generate Roll
+ //
+ float turn = 360;
+
+ if (fabs(angSaveAngles.y - angAbsAngles.y) < fabs(turn))
+ {
+ turn = angSaveAngles.y - angAbsAngles.y;
+ }
+ if (fabs(angSaveAngles.y - angAbsAngles.y + 360) < fabs(turn))
+ {
+ turn = angSaveAngles.y - angAbsAngles.y + 360;
+ }
+ if (fabs(angSaveAngles.y - angAbsAngles.y - 360) < fabs(turn))
+ {
+ turn = angSaveAngles.y - angAbsAngles.y - 360;
+ }
+
+ float speed = m_flFlyingSpeed * 0.4;
+
+ if (fabs(turn) > speed)
+ {
+ if (turn < 0.0)
+ {
+ turn = -speed;
+ }
+ else
+ {
+ turn = speed;
+ }
+ }
+ angAbsAngles.y += turn;
+ angAbsAngles.z -= turn;
+ angAbsAngles.y = fmod((angAbsAngles.y + 360.0), 360.0);
+
+ // don't touch bone controller, makes swim animation look funky with all these hard turns.
+// static float yaw_adj;
+// yaw_adj = yaw_adj * 0.8 + turn;
+// SetBoneController( 0, -yaw_adj / 4.0 );
+
+ //
+ // Roll Smoothing
+ //
+ turn = 360;
+ float flTempRoll = angAbsAngles.z;
+
+ if (fabs(angSaveAngles.z - angAbsAngles.z) < fabs(turn))
+ {
+ turn = angSaveAngles.z - angAbsAngles.z;
+ }
+ if (fabs(angSaveAngles.z - angAbsAngles.z + 360) < fabs(turn))
+ {
+ turn = angSaveAngles.z - angAbsAngles.z + 360;
+ }
+ if (fabs(angSaveAngles.z - angAbsAngles.z - 360) < fabs(turn))
+ {
+ turn = angSaveAngles.z - angAbsAngles.z - 360;
+ }
+ speed = m_flFlyingSpeed/2 * 0.1;
+ if (fabs(turn) < speed)
+ {
+ flTempRoll += turn;
+ }
+ else
+ {
+ if (turn < 0.0)
+ {
+ flTempRoll -= speed;
+ }
+ else
+ {
+ flTempRoll += speed;
+ }
+ }
+
+ angAbsAngles.z = clamp( UTIL_Approach(flTempRoll, angAbsAngles.z, 5 ), -20, 20 );
+
+ SetAbsAngles( angAbsAngles );
+
+ //Move along the current velocity vector
+ if ( WalkMove( m_SaveVelocity * flInterval, MASK_NPCSOLID ) == false )
+ {
+ //Attempt a half-step
+ if ( WalkMove( (m_SaveVelocity*0.5f) * flInterval, MASK_NPCSOLID) == false )
+ {
+ //Restart the velocity
+ //VectorNormalize( m_vecVelocity );
+ m_SaveVelocity *= 0.25f;
+ }
+ else
+ {
+ //Cut our velocity in half
+ m_SaveVelocity *= 0.5f;
+ }
+ }
+
+ SetAbsVelocity( m_SaveVelocity );
+}
+
+void CNPC_Ichthyosaur::InputStartCombat( inputdata_t &input )
+{
+ m_bOnAttack = true;
+}
+
+void CNPC_Ichthyosaur::InputEndCombat( inputdata_t &input )
+{
+ m_bOnAttack = false;
+}
+
+//=========================================================
+// Start task - selects the correct activity and performs
+// any necessary calculations to start the next task on the
+// schedule.
+//=========================================================
+void CNPC_Ichthyosaur::StartTask(const Task_t *pTask)
+{
+ switch (pTask->iTask)
+ {
+ case TASK_ICHTHYOSAUR_CIRCLE_ENEMY:
+ break;
+ case TASK_ICHTHYOSAUR_SWIM:
+ break;
+ case TASK_SMALL_FLINCH:
+ if (m_idealDist > 128)
+ {
+ m_flMaxDist = 512;
+ m_idealDist = 512;
+ }
+ else
+ {
+ m_bOnAttack = true;
+ }
+ BaseClass::StartTask(pTask);
+ break;
+
+ case TASK_ICHTHYOSAUR_FLOAT:
+ m_nSkin = EYE_BASE;
+ SetSequenceByName( "bellyup" );
+ break;
+
+ default:
+ BaseClass::StartTask(pTask);
+ break;
+ }
+}
+
+void CNPC_Ichthyosaur::RunTask(const Task_t *pTask )
+{
+ QAngle angles = GetAbsAngles();
+
+ switch ( pTask->iTask )
+ {
+ case TASK_ICHTHYOSAUR_CIRCLE_ENEMY:
+
+ if (GetEnemy() == NULL )
+ {
+ TaskComplete( );
+ }
+ else if (FVisible( GetEnemy() ))
+ {
+ Vector vecFrom = GetEnemy()->EyePosition( );
+
+ Vector vecDelta = GetAbsOrigin() - vecFrom;
+ VectorNormalize( vecDelta );
+ Vector vecSwim = CrossProduct( vecDelta, Vector( 0, 0, 1 ) );
+ VectorNormalize( vecSwim );
+
+ if (DotProduct( vecSwim, m_SaveVelocity ) < 0)
+ {
+ vecSwim = vecSwim * -1.0;
+ }
+
+ Vector vecPos = vecFrom + vecDelta * m_idealDist + vecSwim * 32;
+
+ trace_t tr;
+
+// UTIL_TraceHull( vecFrom, vecPos, ignore_monsters, large_hull, m_hEnemy->edict(), &tr );
+ UTIL_TraceEntity( this, vecFrom, vecPos, MASK_NPCSOLID, &tr );
+
+ if (tr.fraction > 0.5)
+ {
+ vecPos = tr.endpos;
+ }
+
+ Vector vecNorm = vecPos - GetAbsOrigin();
+ VectorNormalize( vecNorm );
+ m_SaveVelocity = m_SaveVelocity * 0.8 + 0.2 * vecNorm * m_flFlyingSpeed;
+
+ if (HasCondition( COND_ENEMY_FACING_ME ) && GetEnemy()->FVisible( this ))
+ {
+ m_flNextAlert -= 0.1;
+
+ if (m_idealDist < m_flMaxDist)
+ {
+ m_idealDist += 4;
+ }
+ if (m_flFlyingSpeed > m_flMinSpeed)
+ {
+ m_flFlyingSpeed -= 2;
+ }
+ else if (m_flFlyingSpeed < m_flMinSpeed)
+ {
+ m_flFlyingSpeed += 2;
+ }
+ if (m_flMinSpeed < m_flMaxSpeed)
+ {
+ m_flMinSpeed += 0.5;
+ }
+ }
+ else
+ {
+ m_flNextAlert += 0.1;
+
+ if (m_idealDist > 128)
+ {
+ m_idealDist -= 4;
+ }
+ if (m_flFlyingSpeed < m_flMaxSpeed)
+ {
+ m_flFlyingSpeed += 4;
+ }
+ }
+ }
+ else
+ {
+ m_flNextAlert = gpGlobals->curtime + 0.2;
+ }
+
+ if (m_flNextAlert < gpGlobals->curtime)
+ {
+ // ALERT( at_console, "AlertSound()\n");
+ AlertSound( );
+ m_flNextAlert = gpGlobals->curtime + RandomFloat( 3, 5 );
+ }
+
+ break;
+ case TASK_ICHTHYOSAUR_SWIM:
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete( );
+ }
+ break;
+ case TASK_DIE:
+ if ( IsSequenceFinished() )
+ {
+// pev->deadflag = DEAD_DEAD;
+
+ TaskComplete( );
+ }
+ break;
+
+ case TASK_ICHTHYOSAUR_FLOAT:
+ angles.x = UTIL_ApproachAngle( 0, angles.x, 20 );
+ SetAbsAngles( angles );
+
+// SetAbsVelocity( GetAbsVelocity() * 0.8 );
+// if (pev->waterlevel > 1 && GetAbsVelocity().z < 64)
+// {
+// pev->velocity.z += 8;
+// }
+// else
+// {
+// pev->velocity.z -= 8;
+// }
+ // ALERT( at_console, "%f\n", m_vecAbsVelocity.z );
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get our conditions for a melee attack
+// Input : flDot -
+// flDist -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Ichthyosaur::MeleeAttack1Conditions( float flDot, float flDist )
+{
+ // Enemy must be submerged with us
+ if ( GetEnemy() && GetEnemy()->GetWaterLevel() != GetWaterLevel() )
+ return COND_NONE;
+
+ Vector predictedDir = ( (GetEnemy()->GetAbsOrigin()+(GetEnemy()->GetSmoothedVelocity())) - GetAbsOrigin() );
+ float flPredictedDist = VectorNormalize( predictedDir );
+
+ Vector vBodyDir;
+ GetVectors( &vBodyDir, NULL, NULL );
+
+ float flPredictedDot = DotProduct( predictedDir, vBodyDir );
+
+ if ( flPredictedDot < 0.8f )
+ return COND_NOT_FACING_ATTACK;
+
+ if ( ( flPredictedDist > ( GetAbsVelocity().Length() * 0.5f) ) && ( flDist > 128.0f ) )
+ return COND_TOO_FAR_TO_ATTACK;
+
+ return COND_CAN_MELEE_ATTACK1;
+}
+
+//=========================================================
+// RangeAttack1Conditions
+//=========================================================
+int CNPC_Ichthyosaur::RangeAttack1Conditions( float flDot, float flDist )
+{
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if( pEnemy && pEnemy->GetWaterLevel() != GetWaterLevel() )
+ {
+ return COND_NONE;
+ }
+
+ if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 192 && m_idealDist <= 192)))
+ {
+ return COND_CAN_RANGE_ATTACK1;
+ }
+
+ return COND_NONE;
+}
+
+void CNPC_Ichthyosaur::BiteTouch( CBaseEntity *pOther )
+{
+ // bite if we hit who we want to eat
+ if ( pOther == GetEnemy() )
+ {
+ m_flEnemyTouched = gpGlobals->curtime + 0.2f;
+ m_bOnAttack = true;
+ }
+}
+
+#define ICHTHYOSAUR_AE_SHAKE_RIGHT 1
+#define ICHTHYOSAUR_AE_SHAKE_LEFT 2
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Ichthyosaur::HandleAnimEvent( animevent_t *pEvent )
+{
+ int bDidAttack = FALSE;
+ Vector vForward, vRight;
+ QAngle angles = GetAbsAngles();
+ AngleVectors( angles, &vForward, &vRight, NULL );
+
+ switch( pEvent->event )
+ {
+ case ICHTHYOSAUR_AE_SHAKE_RIGHT:
+ case ICHTHYOSAUR_AE_SHAKE_LEFT:
+ {
+ CBaseEntity* hEnemy = GetEnemy();
+
+ if (hEnemy != NULL && FVisible( hEnemy ))
+ {
+ CBaseEntity *pHurt = GetEnemy();
+
+ if ( m_flEnemyTouched > gpGlobals->curtime && (pHurt->BodyTarget( GetAbsOrigin() ) - GetAbsOrigin()).Length() > (32+16+32) )
+ break;
+
+ Vector vecShootOrigin = Weapon_ShootPosition();
+ Vector vecShootDir = GetShootEnemyDir( vecShootOrigin );
+
+ if (DotProduct( vecShootDir, vForward ) > 0.707)
+ {
+ angles = pHurt->GetAbsAngles();
+ m_bOnAttack = true;
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - vRight * 300 );
+ if (pHurt->IsPlayer())
+ {
+ angles.x += RandomFloat( -35, 35 );
+ angles.y += RandomFloat( -90, 90 );
+ angles.z = 0;
+ ((CBasePlayer*) pHurt)->SetPunchAngle( angles );
+ }
+
+ CTakeDamageInfo info( this, this, sk_ichthyosaur_shake.GetInt(), DMG_SLASH );
+ CalculateMeleeDamageForce( &info, vForward, pHurt->GetAbsOrigin() );
+ pHurt->TakeDamage( info );
+ }
+ }
+
+ // Do our bite sound
+ BiteSound();
+
+ bDidAttack = TRUE;
+ }
+ break;
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+ // make bubbles when he attacks
+ if (bDidAttack)
+ {
+ Vector vecSrc = GetAbsOrigin() + vForward * 32;
+ UTIL_Bubbles( vecSrc - Vector( 8, 8, 8 ), vecSrc + Vector( 8, 8, 8 ), 16 );
+ }
+}
+
+//=========================================================
+// Classify - indicates this monster's place in the
+// relationship table.
+//=========================================================
+Class_T CNPC_Ichthyosaur::Classify ( void )
+{
+ return CLASS_ALIEN_MONSTER;
+}
+
+void CNPC_Ichthyosaur::NPCThink ( void )
+{
+ // blink the eye
+ if (m_flBlink < gpGlobals->curtime)
+ {
+ m_nSkin = EYE_CLOSED;
+ if (m_flBlink + 0.2 < gpGlobals->curtime)
+ {
+ m_flBlink = gpGlobals->curtime + random->RandomFloat( 3, 4 );
+ if (m_bOnAttack)
+ m_nSkin = EYE_MAD;
+ else
+ m_nSkin = EYE_BASE;
+ }
+ }
+
+ BaseClass::NPCThink( );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : speed to move at
+//-----------------------------------------------------------------------------
+float CNPC_Ichthyosaur::GetGroundSpeed( void )
+{
+ if ( GetIdealActivity() == ACT_GLIDE )
+ return ICH_SWIM_SPEED_WALK;
+
+ return ICH_SWIM_SPEED_RUN;
+}
+
+Vector CNPC_Ichthyosaur::DoProbe( const Vector &Probe )
+{
+ Vector WallNormal = Vector(0,0,-1); // WATER normal is Straight Down for fish.
+ float frac = 1.0;
+ bool bBumpedSomething = false; // = ProbeZ(GetAbsOrigin(), Probe, &frac);
+
+ trace_t tr;
+ UTIL_TraceEntity( this, GetAbsOrigin(), Probe, MASK_NPCSOLID, &tr );
+ if ( tr.allsolid || tr.fraction < 0.99 )
+ {
+ if (tr.fraction < 0.0) tr.fraction = 0.0;
+ if (tr.fraction > 1.0) tr.fraction = 1.0;
+ if (tr.fraction < frac)
+ {
+ frac = tr.fraction;
+ bBumpedSomething = true;
+ WallNormal = tr.plane.normal;
+ }
+ }
+ //NOTENOTE: Debug start
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 255.0f*(1.0f-tr.fraction), 255.0f * tr.fraction, 0.0f, true, 0.05f );
+ //NOTENOTE: Debug end
+
+ if (bBumpedSomething && (GetEnemy() == NULL || !tr.m_pEnt || tr.m_pEnt->entindex() != GetEnemy()->entindex()))
+ {
+ Vector ProbeDir = Probe - GetAbsOrigin();
+
+ Vector NormalToProbeAndWallNormal = CrossProduct(ProbeDir, WallNormal);
+ Vector SteeringVector = CrossProduct( NormalToProbeAndWallNormal, ProbeDir);
+
+ VectorNormalize( WallNormal );
+ VectorNormalize( m_SaveVelocity );
+
+ float SteeringForce = m_flFlyingSpeed * (1-frac) * ( DotProduct( WallNormal, m_SaveVelocity ) );
+ if (SteeringForce < 0.0)
+ {
+ SteeringForce = -SteeringForce;
+ }
+
+ Vector vSteering = SteeringVector;
+
+ VectorNormalize( vSteering );
+ SteeringVector = SteeringForce * vSteering;
+
+ //NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (SteeringVector*4.0f), 0, 255, 255, true, 0.1f );
+
+ return SteeringVector;
+ }
+ return Vector(0, 0, 0);
+}
+
+bool CNPC_Ichthyosaur::ProbeZ( const Vector &position, const Vector &probe, float *pFraction)
+{
+ int iPositionContents = UTIL_PointContents( position );
+ int iProbeContents = UTIL_PointContents( position );
+
+ if( !(iPositionContents & MASK_WATER) )
+ {
+ // we're not in the water anymore
+ *pFraction = 0.0;
+ return true; // We hit a water boundary because we are where we don't belong.
+ }
+ if( iProbeContents == iPositionContents )
+ {
+ // The probe is entirely inside the water
+ *pFraction = 1.0;
+ return false;
+ }
+
+ Vector ProbeUnit = (probe-position);
+ VectorNormalize( ProbeUnit );
+ float ProbeLength = (probe-position).Length();
+ float maxProbeLength = ProbeLength;
+ float minProbeLength = 0;
+
+ float diff = maxProbeLength - minProbeLength;
+ while (diff > 1.0)
+ {
+ float midProbeLength = minProbeLength + diff/2.0;
+ Vector midProbeVec = midProbeLength * ProbeUnit;
+ if (UTIL_PointContents(position+midProbeVec) == iPositionContents)
+ {
+ minProbeLength = midProbeLength;
+ }
+ else
+ {
+ maxProbeLength = midProbeLength;
+ }
+ diff = maxProbeLength - minProbeLength;
+ }
+ *pFraction = minProbeLength/ProbeLength;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_Ichthyosaur::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ // Can't see entities that aren't in water
+ if ( pEntity->GetWaterLevel() < 1 )
+ return false;
+
+ return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
+}
+
+void CNPC_Ichthyosaur::IdleSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Idle" );
+}
+
+void CNPC_Ichthyosaur::AlertSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Alert" );
+}
+
+void CNPC_Ichthyosaur::AttackSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Attack" );
+}
+
+void CNPC_Ichthyosaur::BiteSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Bite" );
+}
+
+void CNPC_Ichthyosaur::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Die" );
+}
+
+void CNPC_Ichthyosaur::PainSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Ichthyosaur.Pain" );
+}
+
+//-----------------------------------------------------------------------------
+void CNPC_Ichthyosaur::GatherEnemyConditions( CBaseEntity *pEnemy )
+{
+ // Do the base class
+ BaseClass::GatherEnemyConditions( pEnemy );
+
+ if ( HasCondition( COND_ENEMY_UNREACHABLE ) == false )
+ {
+ if( pEnemy == NULL || pEnemy->GetWaterLevel() != GetWaterLevel() )
+ {
+ SetCondition( COND_ENEMY_UNREACHABLE );
+ }
+ }
+}
diff --git a/game/server/hl1/hl1_npc_ichthyosaur.h b/game/server/hl1/hl1_npc_ichthyosaur.h
new file mode 100644
index 0000000..0f05c4d
--- /dev/null
+++ b/game/server/hl1/hl1_npc_ichthyosaur.h
@@ -0,0 +1,97 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_ICHTHYOSAUR_H
+#define NPC_ICHTHYOSAUR_H
+
+
+#include "hl1_ai_basenpc.h"
+
+#define SEARCH_RETRY 16
+
+#define ICHTHYOSAUR_SPEED 150
+
+#define EYE_MAD 0
+#define EYE_BASE 1
+#define EYE_CLOSED 2
+#define EYE_BACK 3
+#define EYE_LOOK 4
+
+
+//
+// CNPC_Ichthyosaur
+//
+
+class CNPC_Ichthyosaur : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Ichthyosaur, CHL1BaseNPC );
+public:
+
+ void Precache( void );
+ void Spawn( void );
+ Class_T Classify ( void );
+ void NPCThink ( void );
+ void Swim ( void );
+ void StartTask(const Task_t *pTask);
+ void RunTask( const Task_t *pTask );
+ int RangeAttack1Conditions( float flDot, float flDist );
+ int MeleeAttack1Conditions ( float flDot, float flDist );
+ void BiteTouch( CBaseEntity *pOther );
+ void HandleAnimEvent( animevent_t *pEvent );
+ int TranslateSchedule( int scheduleType );
+ int SelectSchedule();
+ virtual bool FVisible ( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+
+ Vector DoProbe( const Vector &Probe );
+ bool ProbeZ( const Vector &position, const Vector &probe, float *pFraction);
+
+ float GetGroundSpeed ( void );
+
+ bool OverrideMove( float flInterval );
+ void MoveExecute_Alive(float flInterval);
+
+ void InputStartCombat( inputdata_t &input );
+ void InputEndCombat( inputdata_t &input );
+
+ virtual void IdleSound( void );
+ virtual void AlertSound( void );
+ virtual void DeathSound( const CTakeDamageInfo &info );
+ virtual void PainSound( const CTakeDamageInfo &info );
+
+ void AttackSound( void );
+ void BiteSound( void );
+
+ virtual void GatherEnemyConditions( CBaseEntity *pEnemy );
+
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+
+private:
+ Vector m_SaveVelocity;
+ float m_idealDist;
+
+ float m_flBlink;
+
+ float m_flEnemyTouched;
+ bool m_bOnAttack;
+
+ float m_flMaxSpeed;
+ float m_flMinSpeed;
+ float m_flMaxDist;
+
+ float m_flNextAlert;
+ float m_flLastAttackSound;
+
+ //Save the info from that run
+ Vector m_vecLastMoveTarget;
+ bool m_bHasMoveTarget;
+
+ float m_flFlyingSpeed;
+};
+
+
+#endif //NPC_ICHTHYOSAUR_H
diff --git a/game/server/hl1/hl1_npc_leech.cpp b/game/server/hl1/hl1_npc_leech.cpp
new file mode 100644
index 0000000..9e00fc0
--- /dev/null
+++ b/game/server/hl1/hl1_npc_leech.cpp
@@ -0,0 +1,724 @@
+//========= 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 "hl1_ai_basenpc.h"
+#include "ai_senses.h"
+
+// Animation events
+#define LEECH_AE_ATTACK 1
+#define LEECH_AE_FLOP 2
+
+//#define DEBUG_BEAMS 0
+
+ConVar sk_leech_health( "sk_leech_health", "2" );
+ConVar sk_leech_dmg_bite( "sk_leech_dmg_bite", "2" );
+
+// Movement constants
+
+#define LEECH_ACCELERATE 10
+#define LEECH_CHECK_DIST 45
+#define LEECH_SWIM_SPEED 50
+#define LEECH_SWIM_ACCEL 80
+#define LEECH_SWIM_DECEL 10
+#define LEECH_TURN_RATE 70
+#define LEECH_SIZEX 10
+#define LEECH_FRAMETIME 0.1
+
+class CNPC_Leech : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Leech, CHL1BaseNPC );
+public:
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ static const char *pAlertSounds[];
+
+ void SwimThink( void );
+ void DeadThink( void );
+
+ void SwitchLeechState( void );
+ float ObstacleDistance( CBaseEntity *pTarget );
+ void UpdateMotion( void );
+
+ void RecalculateWaterlevel( void );
+ void Touch( CBaseEntity *pOther );
+
+ Disposition_t IRelationType(CBaseEntity *pTarget);
+
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ void AttackSound( void );
+ void AlertSound( void );
+
+ void Activate( void );
+
+ Class_T Classify( void ) { return CLASS_INSECT; };
+
+ void Event_Killed( const CTakeDamageInfo &info );
+
+
+ bool ShouldGib( const CTakeDamageInfo &info );
+
+
+/* // Base entity functions
+ void Killed( entvars_t *pevAttacker, int iGib );
+ int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );
+*/
+
+private:
+ // UNDONE: Remove unused boid vars, do group behavior
+ float m_flTurning;// is this boid turning?
+ bool m_fPathBlocked;// TRUE if there is an obstacle ahead
+ float m_flAccelerate;
+ float m_obstacle;
+ float m_top;
+ float m_bottom;
+ float m_height;
+ float m_waterTime;
+ float m_sideTime; // Timer to randomly check clearance on sides
+ float m_zTime;
+ float m_stateTime;
+ float m_attackSoundTime;
+ Vector m_oldOrigin;
+};
+
+LINK_ENTITY_TO_CLASS( monster_leech, CNPC_Leech );
+
+BEGIN_DATADESC( CNPC_Leech )
+ DEFINE_FIELD( m_flTurning, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fPathBlocked, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flAccelerate, FIELD_FLOAT ),
+ DEFINE_FIELD( m_obstacle, FIELD_FLOAT ),
+ DEFINE_FIELD( m_top, FIELD_FLOAT ),
+ DEFINE_FIELD( m_bottom, FIELD_FLOAT ),
+ DEFINE_FIELD( m_height, FIELD_FLOAT ),
+ DEFINE_FIELD( m_waterTime, FIELD_TIME ),
+ DEFINE_FIELD( m_sideTime, FIELD_TIME ),
+ DEFINE_FIELD( m_zTime, FIELD_TIME ),
+ DEFINE_FIELD( m_stateTime, FIELD_TIME ),
+ DEFINE_FIELD( m_attackSoundTime, FIELD_TIME ),
+ DEFINE_FIELD( m_oldOrigin, FIELD_VECTOR ),
+
+ DEFINE_THINKFUNC( SwimThink ),
+ DEFINE_THINKFUNC( DeadThink ),
+END_DATADESC()
+
+
+bool CNPC_Leech::ShouldGib( const CTakeDamageInfo &info )
+{
+ return false;
+}
+
+void CNPC_Leech::Spawn( void )
+{
+ Precache();
+ SetModel( "models/leech.mdl" );
+
+ SetHullType(HULL_TINY_CENTERED);
+ SetHullSizeNormal();
+
+ UTIL_SetSize( this, Vector(-1,-1,0), Vector(1,1,2));
+
+ Vector vecSurroundingMins(-8,-8,0);
+ Vector vecSurroundingMaxs(8,8,2);
+ CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs );
+
+ // Don't push the minz down too much or the water check will fail because this entity is really point-sized
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_FLY );
+ AddFlag( FL_SWIM );
+ m_iHealth = sk_leech_health.GetInt();
+
+ m_flFieldOfView = -0.5; // 180 degree FOV
+ SetDistLook( 750 );
+ NPCInit();
+ SetThink( &CNPC_Leech::SwimThink );
+ SetUse( NULL );
+ SetTouch( NULL );
+ SetViewOffset( vec3_origin );
+
+ m_flTurning = 0;
+ m_fPathBlocked = FALSE;
+ SetActivity( ACT_SWIM );
+ SetState( NPC_STATE_IDLE );
+ m_stateTime = gpGlobals->curtime + random->RandomFloat( 1, 5 );
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ m_bloodColor = DONT_BLEED;
+ SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+}
+
+void CNPC_Leech::Activate( void )
+{
+ RecalculateWaterlevel();
+
+ BaseClass::Activate();
+}
+
+void CNPC_Leech::DeadThink( void )
+{
+ if ( IsSequenceFinished() )
+ {
+ if ( GetActivity() == ACT_DIEFORWARD )
+ {
+ SetThink( NULL );
+ StopAnimation();
+ return;
+ }
+ else if ( GetFlags() & FL_ONGROUND )
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetActivity( ACT_DIEFORWARD );
+ }
+ }
+ StudioFrameAdvance();
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ // Apply damage velocity, but keep out of the walls
+ if ( GetAbsVelocity().x != 0 || GetAbsVelocity().y != 0 )
+ {
+ trace_t tr;
+
+ // Look 0.5 seconds ahead
+ UTIL_TraceLine( GetLocalOrigin(), GetLocalOrigin() + GetAbsVelocity() * 0.5, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+ if (tr.fraction != 1.0)
+ {
+ Vector vVelocity = GetAbsVelocity();
+
+ vVelocity.x = 0;
+ vVelocity.y = 0;
+
+ SetAbsVelocity( vVelocity );
+ }
+ }
+}
+
+
+Disposition_t CNPC_Leech::IRelationType( CBaseEntity *pTarget )
+{
+ if ( pTarget->IsPlayer() )
+ return D_HT;
+
+ return BaseClass::IRelationType( pTarget );
+}
+
+void CNPC_Leech::Touch( CBaseEntity *pOther )
+{
+ if ( !pOther->IsPlayer() )
+ return;
+
+ if ( pOther == GetTouchTrace().m_pEnt )
+ {
+ if ( pOther->GetAbsVelocity() == vec3_origin )
+ return;
+
+ SetBaseVelocity( pOther->GetAbsVelocity() );
+ AddFlag( FL_BASEVELOCITY );
+ }
+}
+
+void CNPC_Leech::HandleAnimEvent( animevent_t *pEvent )
+{
+ CBaseEntity *pEnemy = GetEnemy();
+
+ switch( pEvent->event )
+ {
+ case LEECH_AE_FLOP:
+ // Play flop sound
+ break;
+
+ case LEECH_AE_ATTACK:
+ AttackSound();
+
+ if ( pEnemy != NULL )
+ {
+ Vector dir, face;
+
+ AngleVectors( GetAbsAngles(), &face );
+
+ face.z = 0;
+ dir = (pEnemy->GetLocalOrigin() - GetLocalOrigin() );
+ dir.z = 0;
+
+ VectorNormalize( dir );
+ VectorNormalize( face );
+
+ if ( DotProduct(dir, face) > 0.9 ) // Only take damage if the leech is facing the prey
+ {
+ CTakeDamageInfo info( this, this, sk_leech_dmg_bite.GetInt(), DMG_SLASH );
+ CalculateMeleeDamageForce( &info, dir, pEnemy->GetAbsOrigin() );
+ pEnemy->TakeDamage( info );
+ }
+ }
+ m_stateTime -= 2;
+ break;
+
+
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+
+void CNPC_Leech::Precache( void )
+{
+ PrecacheModel("models/leech.mdl");
+
+ PrecacheScriptSound( "Leech.Attack" );
+ PrecacheScriptSound( "Leech.Alert" );
+}
+
+
+void CNPC_Leech::AttackSound( void )
+{
+ if ( gpGlobals->curtime > m_attackSoundTime )
+ {
+ CPASAttenuationFilter filter( this );
+
+ EmitSound(filter, entindex(), "Leech.Attack" );
+ m_attackSoundTime = gpGlobals->curtime + 0.5;
+ }
+}
+
+
+void CNPC_Leech::AlertSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound(filter, entindex(), "Leech.Alert" );
+}
+
+void CNPC_Leech::SwitchLeechState( void )
+{
+ m_stateTime = gpGlobals->curtime + random->RandomFloat( 3, 6 );
+ if ( m_NPCState == NPC_STATE_COMBAT )
+ {
+ SetEnemy ( NULL );
+ SetState( NPC_STATE_IDLE );
+ // We may be up against the player, so redo the side checks
+ m_sideTime = 0;
+ }
+ else
+ {
+ GetSenses()->Look( GetSenses()->GetDistLook() );
+ CBaseEntity *pEnemy = BestEnemy();
+ if ( pEnemy && pEnemy->GetWaterLevel() != 0 )
+ {
+ SetEnemy ( pEnemy );
+ SetState( NPC_STATE_COMBAT );
+ m_stateTime = gpGlobals->curtime + random->RandomFloat( 18, 25 );
+ AlertSound();
+ }
+ }
+}
+
+void CNPC_Leech::RecalculateWaterlevel( void )
+{
+ // Calculate boundaries
+ Vector vecTest = GetLocalOrigin() - Vector(0,0,400);
+
+ trace_t tr;
+
+ UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ if ( tr.fraction != 1.0 )
+ m_bottom = tr.endpos.z + 1;
+ else
+ m_bottom = vecTest.z;
+
+ m_top = UTIL_WaterLevel( GetLocalOrigin(), GetLocalOrigin().z, GetLocalOrigin().z + 400 ) - 1;
+
+#if DEBUG_BEAMS
+ NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + Vector( 0, 0, m_bottom ), 0, 255, 0, false, 0.1f );
+ NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + Vector( 0, 0, m_top ), 0, 255, 255, false, 0.1f );
+#endif
+
+ // Chop off 20% of the outside range
+ float newBottom = m_bottom * 0.8 + m_top * 0.2;
+ m_top = m_bottom * 0.2 + m_top * 0.8;
+ m_bottom = newBottom;
+ m_height = random->RandomFloat( m_bottom, m_top );
+ m_waterTime = gpGlobals->curtime + random->RandomFloat( 5, 7 );
+}
+
+void CNPC_Leech::SwimThink( void )
+{
+ trace_t tr;
+ float flLeftSide;
+ float flRightSide;
+ float targetSpeed;
+ float targetYaw = 0;
+ CBaseEntity *pTarget;
+
+ /*if ( !UTIL_FindClientInPVS( edict() ) )
+ {
+ m_flNextThink = gpGlobals->curtime + random->RandomFloat( 1.0f, 1.5f );
+ SetAbsVelocity( vec3_origin );
+ return;
+ }
+ else*/
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ targetSpeed = LEECH_SWIM_SPEED;
+
+ if ( m_waterTime < gpGlobals->curtime )
+ RecalculateWaterlevel();
+
+ if ( m_stateTime < gpGlobals->curtime )
+ SwitchLeechState();
+
+ ClearCondition( COND_CAN_MELEE_ATTACK1 );
+
+ switch( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+ pTarget = GetEnemy();
+ if ( !pTarget )
+ SwitchLeechState();
+ else
+ {
+ // Chase the enemy's eyes
+ m_height = pTarget->GetLocalOrigin().z + pTarget->GetViewOffset().z - 5;
+ // Clip to viable water area
+ if ( m_height < m_bottom )
+ m_height = m_bottom;
+ else if ( m_height > m_top )
+ m_height = m_top;
+ Vector location = pTarget->GetLocalOrigin() - GetLocalOrigin();
+ location.z += (pTarget->GetViewOffset().z);
+ if ( location.Length() < 80 )
+ SetCondition( COND_CAN_MELEE_ATTACK1 );
+ // Turn towards target ent
+ targetYaw = UTIL_VecToYaw( location );
+
+ QAngle vTestAngle = GetAbsAngles();
+
+ targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( GetAbsAngles().y ) );
+
+ if ( targetYaw < (-LEECH_TURN_RATE) )
+ targetYaw = (-LEECH_TURN_RATE);
+ else if ( targetYaw > (LEECH_TURN_RATE) )
+ targetYaw = (LEECH_TURN_RATE);
+ else
+ targetSpeed *= 2;
+ }
+
+ break;
+
+ default:
+ if ( m_zTime < gpGlobals->curtime )
+ {
+ float newHeight = random->RandomFloat( m_bottom, m_top );
+ m_height = 0.5 * m_height + 0.5 * newHeight;
+ m_zTime = gpGlobals->curtime + random->RandomFloat( 1, 4 );
+ }
+ if ( random->RandomInt( 0, 100 ) < 10 )
+ targetYaw = random->RandomInt( -30, 30 );
+ pTarget = NULL;
+ // oldorigin test
+ if ( ( GetLocalOrigin() - m_oldOrigin ).Length() < 1 )
+ {
+ // If leech didn't move, there must be something blocking it, so try to turn
+ m_sideTime = 0;
+ }
+
+ break;
+ }
+
+ m_obstacle = ObstacleDistance( pTarget );
+ m_oldOrigin = GetLocalOrigin();
+ if ( m_obstacle < 0.1 )
+ m_obstacle = 0.1;
+
+ Vector vForward, vRight;
+
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, NULL );
+
+ // is the way ahead clear?
+ if ( m_obstacle == 1.0 )
+ {
+ // if the leech is turning, stop the trend.
+ if ( m_flTurning != 0 )
+ {
+ m_flTurning = 0;
+ }
+
+ m_fPathBlocked = FALSE;
+ m_flSpeed = UTIL_Approach( targetSpeed, m_flSpeed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME );
+ SetAbsVelocity( vForward * m_flSpeed );
+
+ }
+ else
+ {
+ m_obstacle = 1.0 / m_obstacle;
+ // IF we get this far in the function, the leader's path is blocked!
+ m_fPathBlocked = TRUE;
+
+ if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid
+ {
+ Vector vecTest;
+ // measure clearance on left and right to pick the best dir to turn
+ vecTest = GetLocalOrigin() + ( vRight * LEECH_SIZEX) + ( vForward * LEECH_CHECK_DIST);
+ UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+ flRightSide = tr.fraction;
+
+ vecTest = GetLocalOrigin() + ( vRight * -LEECH_SIZEX) + ( vForward * LEECH_CHECK_DIST);
+ UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ flLeftSide = tr.fraction;
+
+ // turn left, right or random depending on clearance ratio
+ float delta = (flRightSide - flLeftSide);
+ if ( delta > 0.1 || (delta > -0.1 && random->RandomInt( 0,100 ) < 50 ) )
+ m_flTurning = -LEECH_TURN_RATE;
+ else
+ m_flTurning = LEECH_TURN_RATE;
+ }
+
+ m_flSpeed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), m_flSpeed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle );
+ SetAbsVelocity( vForward * m_flSpeed );
+ }
+
+ GetMotor()->SetIdealYaw( m_flTurning + targetYaw );
+ UpdateMotion();
+}
+
+//
+// ObstacleDistance - returns normalized distance to obstacle
+//
+float CNPC_Leech::ObstacleDistance( CBaseEntity *pTarget )
+{
+ trace_t tr;
+ Vector vecTest;
+ Vector vForward, vRight;
+
+ // use VELOCITY, not angles, not all boids point the direction they are flying
+ //Vector vecDir = UTIL_VecToAngles( pev->velocity );
+ QAngle tmp = GetAbsAngles();
+ tmp.x = -tmp.x;
+ AngleVectors ( tmp, &vForward, &vRight, NULL );
+
+ // check for obstacle ahead
+ vecTest = GetLocalOrigin() + vForward * LEECH_CHECK_DIST;
+ UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ if ( tr.startsolid )
+ {
+ m_flSpeed = -LEECH_SWIM_SPEED * 0.5;
+ }
+
+ if ( tr.fraction != 1.0 )
+ {
+ if ( (pTarget == NULL || tr.m_pEnt != pTarget ) )
+ {
+ return tr.fraction;
+ }
+ else
+ {
+ if ( fabs( m_height - GetLocalOrigin().z ) > 10 )
+ return tr.fraction;
+ }
+ }
+
+ if ( m_sideTime < gpGlobals->curtime )
+ {
+ // extra wide checks
+ vecTest = GetLocalOrigin() + vRight * LEECH_SIZEX * 2 + vForward * LEECH_CHECK_DIST;
+ UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ if (tr.fraction != 1.0)
+ return tr.fraction;
+
+ vecTest = GetLocalOrigin() - vRight * LEECH_SIZEX * 2 + vForward * LEECH_CHECK_DIST;
+ UTIL_TraceLine( GetLocalOrigin(), vecTest, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+ if (tr.fraction != 1.0)
+ return tr.fraction;
+
+ // Didn't hit either side, so stop testing for another 0.5 - 1 seconds
+ m_sideTime = gpGlobals->curtime + random->RandomFloat(0.5,1);
+ }
+
+ return 1.0;
+}
+
+void CNPC_Leech::UpdateMotion( void )
+{
+ float flapspeed = ( m_flSpeed - m_flAccelerate) / LEECH_ACCELERATE;
+ m_flAccelerate = m_flAccelerate * 0.8 + m_flSpeed * 0.2;
+
+ if (flapspeed < 0)
+ flapspeed = -flapspeed;
+ flapspeed += 1.0;
+ if (flapspeed < 0.5)
+ flapspeed = 0.5;
+ if (flapspeed > 1.9)
+ flapspeed = 1.9;
+
+ m_flPlaybackRate = flapspeed;
+
+ QAngle vAngularVelocity = GetLocalAngularVelocity();
+ QAngle vAngles = GetLocalAngles();
+
+ if ( !m_fPathBlocked )
+ vAngularVelocity.y = GetMotor()->GetIdealYaw();
+ else
+ vAngularVelocity.y = GetMotor()->GetIdealYaw() * m_obstacle;
+
+ if ( vAngularVelocity.y > 150 )
+ SetIdealActivity( ACT_TURN_LEFT );
+ else if ( vAngularVelocity.y < -150 )
+ SetIdealActivity( ACT_TURN_RIGHT );
+ else
+ SetIdealActivity( ACT_SWIM );
+
+ // lean
+ float targetPitch, delta;
+ delta = m_height - GetLocalOrigin().z;
+
+/* if ( delta < -10 )
+ targetPitch = -30;
+ else if ( delta > 10 )
+ targetPitch = 30;
+ else*/
+ targetPitch = 0;
+
+ vAngles.x = UTIL_Approach( targetPitch, vAngles.x, 60 * LEECH_FRAMETIME );
+
+ // bank
+ vAngularVelocity.z = - ( vAngles.z + (vAngularVelocity.y * 0.25));
+
+ if ( m_NPCState == NPC_STATE_COMBAT && HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ SetIdealActivity( ACT_MELEE_ATTACK1 );
+
+ // Out of water check
+ if ( !GetWaterLevel() )
+ {
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+ SetIdealActivity( ACT_HOP );
+ SetAbsVelocity( vec3_origin );
+
+ // Animation will intersect the floor if either of these is non-zero
+ vAngles.z = 0;
+ vAngles.x = 0;
+
+ m_flPlaybackRate = random->RandomFloat( 0.8, 1.2 );
+ }
+ else if ( GetMoveType() == MOVETYPE_FLYGRAVITY )
+ {
+ SetMoveType( MOVETYPE_FLY );
+ SetGroundEntity( NULL );
+
+ // TODO
+ RecalculateWaterlevel();
+ m_waterTime = gpGlobals->curtime + 2; // Recalc again soon, water may be rising
+ }
+
+ if ( GetActivity() != GetIdealActivity() )
+ {
+ SetActivity ( GetIdealActivity() );
+ }
+ StudioFrameAdvance();
+
+ DispatchAnimEvents ( this );
+
+ SetLocalAngles( vAngles );
+ SetLocalAngularVelocity( vAngularVelocity );
+
+ Vector vForward, vRight;
+
+ AngleVectors( vAngles, &vForward, &vRight, NULL );
+
+#if DEBUG_BEAMS
+ if ( m_fPathBlocked )
+ {
+ float color = m_obstacle * 30;
+ if ( m_obstacle == 1.0 )
+ color = 0;
+ if ( color > 255 )
+ color = 255;
+ NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vForward * LEECH_CHECK_DIST, 255, color, color, false, 0.1f );
+ }
+ else
+ NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vForward * LEECH_CHECK_DIST, 255, 255, 0, false, 0.1f );
+
+ NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + vRight * (vAngularVelocity.y*0.25), 0, 0, 255, false, 0.1f );
+#endif
+
+}
+
+void CNPC_Leech::Event_Killed( const CTakeDamageInfo &info )
+{
+ Vector vecSplatDir;
+ trace_t tr;
+
+ //ALERT(at_aiconsole, "Leech: killed\n");
+ // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality.
+ CBaseEntity *pOwner = GetOwnerEntity();
+ if (pOwner)
+ pOwner->DeathNotice( this );
+
+ // When we hit the ground, play the "death_end" activity
+ if ( GetWaterLevel() )
+ {
+ QAngle qAngles = GetAbsAngles();
+ QAngle qAngularVel = GetLocalAngularVelocity();
+ Vector vOrigin = GetLocalOrigin();
+
+ qAngles.z = 0;
+ qAngles.x = 0;
+
+ vOrigin.z += 1;
+
+ SetAbsVelocity( vec3_origin );
+
+ if ( random->RandomInt( 0, 99 ) < 70 )
+ qAngularVel.y = random->RandomInt( -720, 720 );
+
+ SetAbsAngles( qAngles );
+ SetLocalAngularVelocity( qAngularVel );
+ SetAbsOrigin( vOrigin );
+
+
+ SetGravity ( 0.02 );
+ SetGroundEntity( NULL );
+ SetActivity( ACT_DIESIMPLE );
+ }
+ else
+ SetActivity( ACT_DIEFORWARD );
+
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+ m_takedamage = DAMAGE_NO;
+
+ SetThink( &CNPC_Leech::DeadThink );
+}
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() );
+}
diff --git a/game/server/hl1/hl1_npc_osprey.cpp b/game/server/hl1/hl1_npc_osprey.cpp
new file mode 100644
index 0000000..65b2de8
--- /dev/null
+++ b/game/server/hl1/hl1_npc_osprey.cpp
@@ -0,0 +1,1589 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "beam_shared.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 "hl1_npc_hgrunt.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "animation.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "basecombatweapon.h"
+#include "hl1_basegrenade.h"
+#include "soundenvelope.h"
+#include "hl1_CBaseHelicopter.h"
+#include "IEffects.h"
+#include "smoke_trail.h"
+
+extern short g_sModelIndexFireball;
+
+typedef struct
+{
+ int isValid;
+ EHANDLE hGrunt;
+ Vector vecOrigin;
+ Vector vecAngles;
+} t_ospreygrunt;
+
+#define LOADED_WITH_GRUNTS 0 //WORST NAME EVER!
+#define UNLOADING_GRUNTS 1
+#define GRUNTS_DEPLOYED 2 //Waiting for them to finish repelin
+#define SF_WAITFORTRIGGER 0x40
+#define MAX_CARRY 24
+#define DEFAULT_SPEED 250
+#define HELICOPTER_THINK_INTERVAL 0.1
+
+class CNPC_Osprey : public CBaseHelicopter
+{
+ DECLARE_CLASS( CNPC_Osprey, CBaseHelicopter );
+public:
+
+ int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
+
+ void Spawn( void );
+ void Precache( void );
+ Class_T Classify( void ) { return CLASS_NONE; };
+ int BloodColor( void ) { return DONT_BLEED; }
+
+ void FindAllThink( void );
+ void DeployThink( void );
+ bool HasDead( void );
+ void Flight( void );
+ void HoverThink( void );
+ CAI_BaseNPC *MakeGrunt( Vector vecSrc );
+
+ void InitializeRotorSound( void );
+ void PrescheduleThink( void );
+
+ void DyingThink( void );
+
+ void CrashTouch( CBaseEntity *pOther );
+
+/*
+
+ void CrashTouch( CBaseEntity *pOther );
+ void DyingThink( void );
+ void CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+*/
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+ float m_startTime;
+
+ float m_flIdealtilt;
+ float m_flRotortilt;
+
+ float m_flRightHealth;
+ float m_flLeftHealth;
+
+ int m_iUnits;
+ EHANDLE m_hGrunt[MAX_CARRY];
+ Vector m_vecOrigin[MAX_CARRY];
+ EHANDLE m_hRepel[4];
+
+ int m_iSoundState;
+ int m_iSpriteTexture;
+
+ int m_iPitch;
+
+ int m_iExplode;
+ int m_iTailGibs;
+ int m_iBodyGibs;
+ int m_iEngineGibs;
+
+ int m_iDoLeftSmokePuff;
+ int m_iDoRightSmokePuff;
+
+ int m_iRepelState;
+ float m_flPrevGoalVel;
+
+ int m_iRotorAngle;
+ int m_nDebrisModel;
+
+ CHandle<SmokeTrail> m_hLeftSmoke;
+ CHandle<SmokeTrail> m_hRightSmoke;
+
+ DECLARE_DATADESC();
+
+ int m_iNextCrashModel; //which gib to explode with next
+};
+
+LINK_ENTITY_TO_CLASS( monster_osprey, CNPC_Osprey );
+
+BEGIN_DATADESC( CNPC_Osprey )
+ DEFINE_FIELD( m_startTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flIdealtilt, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flRotortilt, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_flRightHealth, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLeftHealth, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_iRepelState, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_iUnits, FIELD_INTEGER ),
+ DEFINE_ARRAY( m_hGrunt, FIELD_EHANDLE, MAX_CARRY ),
+ DEFINE_ARRAY( m_vecOrigin, FIELD_POSITION_VECTOR, MAX_CARRY ),
+ DEFINE_ARRAY( m_hRepel, FIELD_EHANDLE, 4 ),
+
+ // DEFINE_FIELD( m_iTailGibs, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_iBodyGibs, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_iEngineGibs, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_nDebrisModel, FIELD_INTEGER ),
+
+ // DEFINE_FIELD( m_iSoundState, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_iPitch, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_flPrevGoalVel, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iRotorAngle, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_hLeftSmoke, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hRightSmoke, FIELD_EHANDLE ),
+
+ DEFINE_FIELD( m_iNextCrashModel, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_iDoLeftSmokePuff, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iDoRightSmokePuff, FIELD_INTEGER ),
+
+ //DEFINE_FIELD( m_iExplode, FIELD_INTEGER ),
+
+ DEFINE_THINKFUNC( FindAllThink ),
+ DEFINE_THINKFUNC( DeployThink ),
+
+ DEFINE_ENTITYFUNC( CrashTouch ),
+
+/* DEFINE_FUNCTION ( HoverThink ),
+ DEFINE_FUNCTION ( DyingThink ),
+ DEFINE_FUNCTION ( CommandUse ),*/
+END_DATADESC()
+
+
+void CNPC_Osprey::Spawn( void )
+{
+ Precache( );
+ // motor
+ SetModel( "models/osprey.mdl" );
+
+ BaseClass::Spawn();
+
+ Vector mins, maxs;
+ ExtractBbox( 0, mins, maxs );
+ UTIL_SetSize( this, mins, maxs );
+ UTIL_SetOrigin( this, GetAbsOrigin() );
+
+ AddFlag( FL_NPC );
+ m_takedamage = DAMAGE_YES;
+ m_flRightHealth = 200;
+ m_flLeftHealth = 200;
+ m_iHealth = 400;
+
+ m_flFieldOfView = 0; // 180 degrees
+
+ SetSequence( 0 );
+ ResetSequenceInfo( );
+ SetCycle( random->RandomInt( 0,0xFF ) );
+
+// InitBoneControllers();
+
+ m_startTime = gpGlobals->curtime + 1;
+
+ //FindAllThink();
+// SetUse( CommandUse );
+
+/* if (!( m_spawnflags & SF_WAITFORTRIGGER))
+ {
+ SetThink( gpGlobals->curtime + 1.0 );
+ }*/
+
+ m_flMaxSpeed = (float)BASECHOPPER_MAX_SPEED / 2;
+
+ m_iRepelState = LOADED_WITH_GRUNTS;
+ m_flPrevGoalVel = 9999;
+
+ m_iRotorAngle = -1;
+ SetBoneController( 0, m_iRotorAngle );
+
+ m_hLeftSmoke = NULL;
+ m_hRightSmoke = NULL;
+}
+
+
+void CNPC_Osprey::Precache( void )
+{
+ UTIL_PrecacheOther( "monster_human_grunt" );
+
+ PrecacheModel("models/osprey.mdl");
+ PrecacheModel("models/HVR.mdl");
+
+ m_iSpriteTexture = PrecacheModel( "sprites/rope.vmt" );
+
+ m_iExplode = PrecacheModel( "sprites/fexplo.vmt" );
+ m_iTailGibs = PrecacheModel( "models/osprey_tailgibs.mdl" );
+ m_iBodyGibs = PrecacheModel( "models/osprey_bodygibs.mdl" );
+ m_iEngineGibs = PrecacheModel( "models/osprey_enginegibs.mdl" );
+
+ m_nDebrisModel = PrecacheModel( "models/mechgibs.mdl" );
+
+ PrecacheScriptSound( "Apache.RotorSpinup" );
+
+ BaseClass::Precache();
+}
+
+void CNPC_Osprey::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ float flDamage = info.GetDamage();
+
+ // Hit right engine
+ if (ptr->hitgroup == 3 )
+ {
+ if( m_flRightHealth <= 0 )
+ return;
+ else
+ m_flRightHealth -= flDamage;
+
+ if( m_flRightHealth <= 0 )
+ {
+ Assert( m_hRightSmoke == NULL );
+
+ if ( (m_hRightSmoke = SmokeTrail::CreateSmokeTrail()) != NULL )
+ {
+ m_hRightSmoke->m_Opacity = 1.0f;
+ m_hRightSmoke->m_SpawnRate = 60;
+ m_hRightSmoke->m_ParticleLifetime = 1.3f;
+ m_hRightSmoke->m_StartColor.Init( 0.65f, 0.65f , 0.65f );
+ m_hRightSmoke->m_EndColor.Init( 0.65f, 0.65f, 0.65f );
+ m_hRightSmoke->m_StartSize = 12;
+ m_hRightSmoke->m_EndSize = 80;
+ m_hRightSmoke->m_SpawnRadius = 8;
+ m_hRightSmoke->m_MinSpeed = 2;
+ m_hRightSmoke->m_MaxSpeed = 24;
+
+ m_hRightSmoke->SetLifetime( 1e6 );
+ m_hRightSmoke->FollowEntity( this, "right" );
+ }
+ }
+ }
+
+ // Hit left engine
+ if (ptr->hitgroup == 2 )
+ {
+ if( m_flLeftHealth <= 0 )
+ return;
+ else
+ m_flLeftHealth -= flDamage;
+
+ if( m_flLeftHealth <= 0 )
+ {
+ //create smoke trail
+ Assert( m_hLeftSmoke == NULL );
+
+ if ( (m_hLeftSmoke = SmokeTrail::CreateSmokeTrail()) != NULL )
+ {
+ m_hLeftSmoke->m_Opacity = 1.0f;
+ m_hLeftSmoke->m_SpawnRate = 60;
+ m_hLeftSmoke->m_ParticleLifetime = 1.3f;
+ m_hLeftSmoke->m_StartColor.Init( 0.65f, 0.65f , 0.65f );
+ m_hLeftSmoke->m_EndColor.Init( 0.65f, 0.65f, 0.65f );
+ m_hLeftSmoke->m_StartSize = 12;
+ m_hLeftSmoke->m_EndSize = 64;
+ m_hLeftSmoke->m_SpawnRadius = 8;
+ m_hLeftSmoke->m_MinSpeed = 2;
+ m_hLeftSmoke->m_MaxSpeed = 24;
+
+ m_hLeftSmoke->SetLifetime( 1e6 );
+ m_hLeftSmoke->FollowEntity( this, "left" );
+ }
+ }
+ }
+
+ // hit hard, hits cockpit, hits engines
+ if (flDamage > 50 || ptr->hitgroup == 1 || ptr->hitgroup == 2 || ptr->hitgroup == 3)
+ {
+ AddMultiDamage( info, this );
+ }
+ else
+ {
+ g_pEffects->Sparks( ptr->endpos );
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Osprey::InitializeRotorSound( void )
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+ m_pRotorSound = controller.SoundCreate( filter, entindex(), CHAN_STATIC, "Apache.RotorSpinup", 0.2 );
+
+ BaseClass::InitializeRotorSound();
+}
+
+void CNPC_Osprey::FindAllThink( void )
+{
+ CBaseEntity *pEntity = NULL;
+
+ m_iUnits = 0;
+ while ( ( pEntity = gEntList.FindEntityByClassname( pEntity, "monster_human_grunt" ) ) != NULL)
+ {
+ if ( m_iUnits > MAX_CARRY )
+ break;
+
+ if (pEntity->IsAlive())
+ {
+ m_hGrunt[m_iUnits] = pEntity;
+ m_vecOrigin[m_iUnits] = pEntity->GetAbsOrigin();
+ m_iUnits++;
+ }
+ }
+
+ if (m_iUnits == 0)
+ {
+ Msg( "osprey error: no grunts to resupply\n");
+ UTIL_Remove( this );
+ return;
+ }
+
+ m_startTime = 0.0f;
+}
+
+void CNPC_Osprey::DeployThink( void )
+{
+ Vector vecForward;
+ Vector vecRight;
+ Vector vecUp;
+ Vector vecSrc;
+
+ SetLocalAngularVelocity( QAngle ( 0, 0, 0 ) );
+ SetAbsVelocity( Vector ( 0, 0, 0 ) );
+
+ AngleVectors( GetAbsAngles(), &vecForward, &vecRight, &vecUp );
+
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -4096.0), MASK_SOLID_BRUSHONLY, this,COLLISION_GROUP_NONE, &tr);
+ CSoundEnt::InsertSound ( SOUND_DANGER, tr.endpos, 400, 0.3 );
+
+ vecSrc = GetAbsOrigin() + vecForward * 32 + vecRight * 100 + vecUp * -96;
+ m_hRepel[0] = MakeGrunt( vecSrc );
+
+ vecSrc = GetAbsOrigin() + vecForward * -64 + vecRight * 100 + vecUp * -96;
+ m_hRepel[1] = MakeGrunt( vecSrc );
+
+ vecSrc = GetAbsOrigin() + vecForward * 32 + vecRight * -100 + vecUp * -96;
+ m_hRepel[2] = MakeGrunt( vecSrc );
+
+ vecSrc = GetAbsOrigin() + vecForward * -64 + vecRight * -100 + vecUp * -96;
+ m_hRepel[3] = MakeGrunt( vecSrc );
+
+ m_iRepelState = GRUNTS_DEPLOYED;
+
+ HoverThink();
+}
+
+bool CNPC_Osprey::HasDead( )
+{
+ for (int i = 0; i < m_iUnits; i++)
+ {
+ if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive())
+ {
+ return TRUE;
+ }
+ else
+ {
+ m_vecOrigin[i] = m_hGrunt[i]->GetAbsOrigin(); // send them to where they died
+ }
+ }
+ return FALSE;
+}
+
+void CNPC_Osprey::HoverThink( void )
+{
+ int i = 0;
+ for (i = 0; i < 4; i++)
+ {
+ CBaseEntity *pRepel = (CBaseEntity*)m_hRepel[i];
+
+ if ( pRepel != NULL && pRepel->m_iHealth > 0 && pRepel->GetMoveType() == MOVETYPE_FLYGRAVITY )
+ {
+ break;
+ }
+ }
+
+ if ( i == 4 )
+ m_iRepelState = LOADED_WITH_GRUNTS;
+
+ if( m_iRepelState != LOADED_WITH_GRUNTS )
+ {
+ // angle of engines should approach vertical
+ m_iRotorAngle = UTIL_Approach(0, m_iRotorAngle, 5);
+ }
+}
+
+CAI_BaseNPC *CNPC_Osprey::MakeGrunt( Vector vecSrc )
+{
+ CBaseEntity *pEntity;
+ CAI_BaseNPC *pGrunt;
+
+ trace_t tr;
+ UTIL_TraceLine( vecSrc, vecSrc + Vector( 0, 0, -4096.0), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ if ( tr.m_pEnt && tr.m_pEnt->GetSolid() != SOLID_BSP)
+ return NULL;
+
+ for (int i = 0; i < m_iUnits; i++)
+ {
+ if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive())
+ {
+ if (m_hGrunt[i] != NULL && m_hGrunt[i]->m_nRenderMode == kRenderNormal)
+ {
+ m_hGrunt[i]->SUB_StartFadeOut( );
+ }
+ pEntity = Create( "monster_human_grunt", vecSrc, GetAbsAngles() );
+ pGrunt = pEntity->MyNPCPointer( );
+ pGrunt->SetMoveType( MOVETYPE_FLYGRAVITY );
+ pGrunt->SetGravity( 0.0001 );
+
+ Vector spd = Vector( 0, 0, random->RandomFloat( -196, -128 ) );
+ pGrunt->SetLocalVelocity( spd );
+ pGrunt->SetActivity( ACT_GLIDE );
+ pGrunt->SetGroundEntity( NULL );
+
+ pGrunt->SetOwnerEntity(this);
+
+
+ CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.vmt", 1.0 );
+ pBeam->PointEntInit( vecSrc + Vector(0,0,112), pGrunt );
+ pBeam->SetBeamFlags( FBEAM_SOLID );
+ pBeam->SetColor( 255, 255, 255 );
+ pBeam->SetThink( &CBaseEntity::SUB_Remove );
+ pBeam->SetNextThink( gpGlobals->curtime + -4096.0 * tr.fraction / pGrunt->GetAbsVelocity().z + 0.5 );
+
+
+ // ALERT( at_console, "%d at %.0f %.0f %.0f\n", i, m_vecOrigin[i].x, m_vecOrigin[i].y, m_vecOrigin[i].z );
+ pGrunt->m_vecLastPosition = m_vecOrigin[i];
+ m_hGrunt[i] = pGrunt;
+ return pGrunt;
+ }
+ }
+ // ALERT( at_console, "none dead\n");
+ return NULL;
+}
+
+void CNPC_Osprey::Flight( void )
+{
+ if ( m_iRepelState == LOADED_WITH_GRUNTS )
+ {
+ BaseClass::Flight();
+
+ // adjust angle of osprey rotors
+ if ( m_angleVelocity > 0 )
+ {
+ m_iRotorAngle = UTIL_Approach(-45, m_iRotorAngle, 5 * (m_angleVelocity / 10));
+ }
+ else
+ {
+ m_iRotorAngle = UTIL_Approach(-1, m_iRotorAngle, 5);
+ }
+ SetBoneController( 0, m_iRotorAngle );
+
+ }
+}
+
+void CNPC_Osprey::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ StudioFrameAdvance( );
+
+ if ( m_startTime != 0.0 && m_startTime <= gpGlobals->curtime )
+ FindAllThink();
+
+ if ( GetGoalEnt() )
+ {
+ if ( m_flPrevGoalVel != GetGoalEnt()->m_flSpeed )
+ {
+ if ( m_flPrevGoalVel == 0 && GetGoalEnt()->m_flSpeed != 0 )
+ {
+ if ( HasDead() && m_iRepelState == LOADED_WITH_GRUNTS )
+ m_iRepelState = UNLOADING_GRUNTS;
+ }
+
+ m_flPrevGoalVel = GetGoalEnt()->m_flSpeed;
+ }
+ }
+
+ if ( m_iRepelState == UNLOADING_GRUNTS )
+ DeployThink();
+ else if ( m_iRepelState == GRUNTS_DEPLOYED )
+ HoverThink();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Lame, temporary death
+//-----------------------------------------------------------------------------
+void CNPC_Osprey::DyingThink( void )
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if( gpGlobals->curtime > m_flNextCrashExplosion )
+ {
+ CPASFilter filter( GetAbsOrigin() );
+ Vector pos;
+ QAngle dummy;
+
+ int rand = RandomInt(0,10);
+
+ if( rand < 4 )
+ {
+ int iAttach = LookupAttachment( rand % 2 ? "left" : "right" );
+ GetAttachment( iAttach, pos, dummy );
+ }
+ else
+ {
+ pos = GetAbsOrigin();
+ pos.x += random->RandomFloat( -150, 150 );
+ pos.y += random->RandomFloat( -150, 150 );
+ pos.z += random->RandomFloat( -150, -50 );
+ }
+
+ te->Explosion( filter, 0.0, &pos, g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0 );
+ m_flNextCrashExplosion = gpGlobals->curtime + random->RandomFloat( 0.4, 0.7 );
+
+ Vector vecSize = Vector( 500, 500, 60 );
+
+ switch( m_iNextCrashModel )
+ {
+ case 0:
+ {
+ te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle,
+ vecSize, vec3_origin, m_iTailGibs, 100, 0, 2.5, BREAK_METAL );
+ break;
+ }
+ case 1:
+ {
+ te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle,
+ vecSize, vec3_origin, m_iBodyGibs, 100, 0, 2.5, BREAK_METAL );
+ break;
+ }
+ case 2:
+ {
+ te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle,
+ vecSize, vec3_origin, m_iEngineGibs, 100, 0, 2.5, BREAK_METAL );
+ break;
+ }
+ case 3:
+ {
+ te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle,
+ vecSize, vec3_origin, m_nDebrisModel, 100, 0, 2.5, BREAK_METAL );
+ break;
+ }
+ }
+
+ m_iNextCrashModel++;
+ if( m_iNextCrashModel > 3 ) m_iNextCrashModel = 0;
+ }
+
+ QAngle angVel = GetLocalAngularVelocity();
+ if( angVel.y < 400 )
+ {
+ angVel.y *= 1.1;
+ SetLocalAngularVelocity( angVel );
+ }
+ Vector vecImpulse( 0, 0, 0 );
+ // add gravity
+ vecImpulse.z -= 38.4; // 32ft/sec
+ ApplyAbsVelocityImpulse( vecImpulse );
+
+}
+
+void CNPC_Osprey::CrashTouch( CBaseEntity *pOther )
+{
+ Vector vecSize = Vector( 120, 120, 30 );
+ CPVSFilter filter( GetAbsOrigin() );
+
+ te->BreakModel( filter, 0.0, GetAbsOrigin(), vec3_angle,
+ vecSize, vec3_origin, m_iTailGibs, 100, 0, 2.5, BREAK_METAL );
+
+ if( m_hLeftSmoke )
+ {
+ m_hLeftSmoke->SetLifetime(0.1f);
+ m_hLeftSmoke = NULL;
+ }
+
+ if( m_hRightSmoke )
+ {
+ m_hRightSmoke->SetLifetime(0.1f);
+ m_hRightSmoke = NULL;
+ }
+
+ BaseClass::CrashTouch( pOther );
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+BEGIN_DATADESC( CBaseHelicopter )
+
+ DEFINE_THINKFUNC( HelicopterThink ),
+ DEFINE_THINKFUNC( CallDyingThink ),
+ DEFINE_ENTITYFUNC( CrashTouch ),
+ DEFINE_ENTITYFUNC( FlyTouch ),
+
+ DEFINE_SOUNDPATCH( m_pRotorSound ),
+
+ DEFINE_FIELD( m_flForce, FIELD_FLOAT ),
+ DEFINE_FIELD( m_fHelicopterFlags, FIELD_INTEGER),
+ DEFINE_FIELD( m_vecDesiredFaceDir, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecDesiredPosition,FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecGoalOrientation,FIELD_VECTOR ),
+ DEFINE_FIELD( m_flLastSeen, FIELD_TIME ),
+ DEFINE_FIELD( m_flPrevSeen, FIELD_TIME ),
+// DEFINE_FIELD( m_iSoundState, FIELD_INTEGER ), // Don't save, precached
+ DEFINE_FIELD( m_vecTarget, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecTargetPosition, FIELD_POSITION_VECTOR ),
+
+ DEFINE_FIELD( m_angleVelocity, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextCrashExplosion, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flMaxSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flMaxSpeedFiring, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flGoalSpeed, FIELD_FLOAT ),
+ DEFINE_KEYFIELD( m_flInitialSpeed, FIELD_FLOAT, "InitialSpeed" ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_STRING, "ChangePathCorner", InputChangePathCorner),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate),
+
+ // Outputs
+ DEFINE_OUTPUT(m_AtTarget, "AtPathCorner" ),
+ DEFINE_OUTPUT(m_LeaveTarget, "LeavePathCorner" ),//<<TEMP>> Undone
+
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CBaseHelicopter, DT_BaseHelicopter )
+END_SEND_TABLE()
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+// Notes : Have your derived Helicopter's Spawn() function call this one FIRST
+//------------------------------------------------------------------------------
+void CBaseHelicopter::Precache( void )
+{
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+// Notes : Have your derived Helicopter's Spawn() function call this one FIRST
+//------------------------------------------------------------------------------
+void CBaseHelicopter::Spawn( void )
+{
+ Precache( );
+
+ SetSolid( SOLID_BBOX );
+ SetMoveType( MOVETYPE_STEP );
+ AddFlag( FL_FLY );
+
+ m_lifeState = LIFE_ALIVE;
+
+ // This base class assumes the helicopter has no guns or missiles.
+ // Set the appropriate flags in your derived class' Spawn() function.
+ m_fHelicopterFlags &= ~BITS_HELICOPTER_MISSILE_ON;
+ m_fHelicopterFlags &= ~BITS_HELICOPTER_GUN_ON;
+
+ m_pRotorSound = NULL;
+
+ SetCycle( 0 );
+ ResetSequenceInfo();
+
+ AddFlag( FL_NPC );
+
+ m_flMaxSpeed = BASECHOPPER_MAX_SPEED;
+ m_flMaxSpeedFiring = BASECHOPPER_MAX_FIRING_SPEED;
+ m_takedamage = DAMAGE_AIM;
+
+ // Don't start up if the level designer has asked the
+ // helicopter to start disabled.
+ if ( !(m_spawnflags & SF_AWAITINPUT) )
+ {
+ Startup();
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ }
+
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+bool CBaseHelicopter::FireGun( void )
+{
+ return true;
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : The main think function for the helicopters
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::HelicopterThink( void )
+{
+ SetNextThink( gpGlobals->curtime + HELICOPTER_THINK_INTERVAL );
+
+ // Don't keep this around for more than one frame.
+ ClearCondition( COND_ENEMY_DEAD );
+
+ // Animate and dispatch animation events.
+ DispatchAnimEvents( this );
+
+ PrescheduleThink();
+
+ ShowDamage( );
+
+ // -----------------------------------------------
+ // If AI is disabled, kill any motion and return
+ // -----------------------------------------------
+ if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI)
+ {
+ SetAbsVelocity( vec3_origin );
+ SetLocalAngularVelocity( vec3_angle );
+ SetNextThink( gpGlobals->curtime + HELICOPTER_THINK_INTERVAL );
+ return;
+ }
+
+ Hunt();
+
+ HelicopterPostThink();
+}
+
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::Hunt( void )
+{
+ FlyPathCorners( );
+
+ if( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ SetEnemy( NULL );
+ }
+
+ // Look for my best enemy. If I change enemies,
+ // be sure and change my prevseen/lastseen timers.
+ if( m_lifeState == LIFE_ALIVE )
+ {
+ GetSenses()->Look( 4092 );
+
+ ChooseEnemy();
+
+ if( HasEnemy() )
+ {
+ CheckEnemy( GetEnemy() );
+
+ if (FVisible( GetEnemy() ))
+ {
+ if (m_flLastSeen < gpGlobals->curtime - 2)
+ {
+ m_flPrevSeen = gpGlobals->curtime;
+ }
+
+ m_flLastSeen = gpGlobals->curtime;
+ m_vecTargetPosition = GetEnemy()->WorldSpaceCenter();
+ }
+ }
+ else
+ {
+ // look at where we're going instead
+ m_vecTargetPosition = m_vecDesiredPosition;
+ }
+ }
+ else
+ {
+ // If we're dead or dying, forget our enemy and don't look for new ones(sjb)
+ SetEnemy( NULL );
+ }
+
+ if ( 1 )
+ {
+ Vector targetDir = m_vecTargetPosition - GetAbsOrigin();
+ Vector desiredDir = m_vecDesiredPosition - GetAbsOrigin();
+
+ VectorNormalize( targetDir );
+ VectorNormalize( desiredDir );
+
+ if ( !IsCrashing() && m_flLastSeen + 5 > gpGlobals->curtime ) //&& DotProduct( targetDir, desiredDir) > 0.25)
+ {
+ // If we've seen the target recently, face the target.
+ //Msg( "Facing Target \n" );
+ m_vecDesiredFaceDir = targetDir;
+ }
+ else
+ {
+ // Face our desired position.
+ // Msg( "Facing Position\n" );
+ m_vecDesiredFaceDir = desiredDir;
+ }
+ }
+ else
+ {
+ // Face the way the path corner tells us to.
+ //Msg( "Facing my path corner\n" );
+ m_vecDesiredFaceDir = m_vecGoalOrientation;
+ }
+
+ Flight();
+
+ UpdatePlayerDopplerShift( );
+
+ // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->curtime, m_flLastSeen, m_flPrevSeen );
+ if (m_fHelicopterFlags & BITS_HELICOPTER_GUN_ON)
+ {
+ if ( (m_flLastSeen + 1 > gpGlobals->curtime) && (m_flPrevSeen + 2 < gpGlobals->curtime) )
+ {
+ if (FireGun( ))
+ {
+ // slow down if we're firing
+ if (m_flGoalSpeed > m_flMaxSpeedFiring )
+ {
+ m_flGoalSpeed = m_flMaxSpeedFiring;
+ }
+ }
+ }
+ }
+
+ if (m_fHelicopterFlags & BITS_HELICOPTER_MISSILE_ON)
+ {
+ AimRocketGun();
+ }
+
+ // Finally, forget dead enemies.
+ if( GetEnemy() != NULL && !GetEnemy()->IsAlive() )
+ {
+ SetEnemy( NULL );
+ }
+}
+
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::FlyPathCorners( void )
+{
+
+ if ( GetGoalEnt() == NULL && m_target != NULL_STRING )// this monster has a target
+ {
+ SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) );
+ if (GetGoalEnt())
+ {
+ m_vecDesiredPosition = GetGoalEnt()->GetLocalOrigin();
+
+ // FIXME: orienation removed from path_corners!
+ AngleVectors( GetGoalEnt()->GetLocalAngles(), &m_vecGoalOrientation );
+ }
+ }
+
+ // walk route
+ if (GetGoalEnt())
+ {
+ // ALERT( at_console, "%.0f\n", flLength );
+ if ( HasReachedTarget( ) )
+ {
+ // If we get this close to the desired position, it's assumed that we've reached
+ // the desired position, so move on.
+
+ // Fire target that I've reached my goal
+ m_AtTarget.FireOutput( GetGoalEnt(), this );
+
+ OnReachedTarget( GetGoalEnt() );
+
+ SetGoalEnt( gEntList.FindEntityByName( NULL, GetGoalEnt()->m_target ) );
+
+ if (GetGoalEnt())
+ {
+ m_vecDesiredPosition = GetGoalEnt()->GetAbsOrigin();
+
+ // FIXME: orienation removed from path_corners!
+ AngleVectors( GetGoalEnt()->GetLocalAngles(), &m_vecGoalOrientation );
+
+ // NDebugOverlay::Box( m_vecDesiredPosition, Vector( -16, -16, -16 ), Vector( 16, 16, 16 ), 0,255,0, false, 30.0);
+ }
+ }
+ }
+ else
+ {
+ // If we can't find a new target, just stay where we are.
+ m_vecDesiredPosition = GetAbsOrigin();
+ }
+
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::UpdatePlayerDopplerShift( )
+{
+ // -----------------------------
+ // make rotor, engine sounds
+ // -----------------------------
+ if (m_iSoundState == 0)
+ {
+ // Sound startup.
+ InitializeRotorSound();
+ }
+ else
+ {
+ CBaseEntity *pPlayer = NULL;
+
+ // UNDONE: this needs to send different sounds to every player for multiplayer.
+ // FIXME: this isn't the correct way to find a player!!!
+ pPlayer = gEntList.FindEntityByName( NULL, "!player" );
+ if (pPlayer)
+ {
+ Vector dir = pPlayer->GetLocalOrigin() - GetLocalOrigin();
+ VectorNormalize(dir);
+
+ float velReceiver = -DotProduct( pPlayer->GetAbsVelocity(), dir );
+ float velTransmitter = -DotProduct( GetAbsVelocity(), dir );
+ // speed of sound == 13049in/s
+ int iPitch = 100 * ((1 - velReceiver / 13049) / (1 + velTransmitter / 13049));
+
+ // clamp pitch shifts
+ if (iPitch > 250)
+ iPitch = 250;
+ if (iPitch < 50)
+ iPitch = 50;
+
+ // Msg( "Pitch:%d\n", iPitch );
+
+ UpdateRotorSoundPitch( iPitch );
+ //Msg( "%.0f\n", pitch );
+ //Msg( "%.0f\n", flVol );
+ }
+ else
+ {
+ Msg( "Chopper didn't find a player!\n" );
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+void CBaseHelicopter::Flight( void )
+{
+ if( GetFlags() & FL_ONGROUND )
+ {
+ //This would be really bad.
+ SetGroundEntity( NULL );
+ }
+
+ // Generic speed up
+ if (m_flGoalSpeed < m_flMaxSpeed)
+ {
+ m_flGoalSpeed += GetAcceleration();
+ }
+
+// NDebugOverlay::Line(GetAbsOrigin(), m_vecDesiredPosition, 0,0,255, true, 0.1);
+
+ // estimate where I'll be facing in one seconds
+ Vector forward, right, up;
+ AngleVectors( GetLocalAngles() + GetLocalAngularVelocity() * 2, &forward, &right, &up );
+
+ QAngle angVel = GetLocalAngularVelocity();
+ float flSide = DotProduct( m_vecDesiredFaceDir, right );
+ if (flSide < 0)
+ {
+ if ( angVel.y < 8 )
+ {
+ angVel.y += 2;
+ }
+ else if (angVel.y < 60)
+ {
+ angVel.y += 8;
+ }
+ }
+ else
+ {
+ if ( angVel.y > -8 )
+ {
+ angVel.y -= 2;
+ }
+ else if (angVel.y > -60)
+ {
+ angVel.y -= 8;
+ }
+ }
+
+ angVel.y *= ( 0.98 ); // why?! (sjb)
+
+ // estimate where I'll be in two seconds
+ AngleVectors( GetLocalAngles() + angVel, NULL, NULL, &up );
+ Vector vecEst = GetAbsOrigin() + GetAbsVelocity() * 2.0 + up * m_flForce * 20 - Vector( 0, 0, 384 * 2 );
+
+ // add immediate force
+ AngleVectors( GetLocalAngles(), &forward, &right, &up );
+
+ Vector vecImpulse( 0, 0, 0 );
+ vecImpulse.x += up.x * m_flForce;
+ vecImpulse.y += up.y * m_flForce;
+ vecImpulse.z += up.z * m_flForce;
+
+ // add gravity
+ vecImpulse.z -= 38.4; // 32ft/sec
+ ApplyAbsVelocityImpulse( vecImpulse );
+
+ float flSpeed = GetAbsVelocity().Length();
+ float flDir = DotProduct( Vector( forward.x, forward.y, 0 ), Vector( GetAbsVelocity().x, GetAbsVelocity().y, 0 ) );
+ if (flDir < 0)
+ {
+ flSpeed = -flSpeed;
+ }
+
+ float flDist = DotProduct( m_vecDesiredPosition - vecEst, forward );
+
+// float flDist = (m_vecDesiredPosition - vecEst).Length();
+
+ // float flSlip = DotProduct( GetAbsVelocity(), right );
+ float flSlip = -DotProduct( m_vecDesiredPosition - vecEst, right );
+
+ // fly sideways
+ if (flSlip > 0)
+ {
+ if (GetLocalAngles().z > -30 && angVel.z > -15)
+ angVel.z -= 4;
+ else
+ angVel.z += 2;
+ }
+ else
+ {
+ if (GetLocalAngles().z < 30 && angVel.z < 15)
+ angVel.z += 4;
+ else
+ angVel.z -= 2;
+ }
+
+ // These functions contain code Ken wrote that used to be right here as part of the flight model,
+ // but we want different helicopter vehicles to have different drag characteristics, so I made
+ // them virtual functions (sjb)
+ ApplySidewaysDrag( right );
+ ApplyGeneralDrag();
+
+ // apply power to stay correct height
+ // FIXME: these need to be per class variables
+#define MAX_FORCE 80
+#define FORCE_POSDELTA 12
+#define FORCE_NEGDELTA 8
+
+ if (m_flForce < MAX_FORCE && vecEst.z < m_vecDesiredPosition.z)
+ {
+ m_flForce += FORCE_POSDELTA;
+ }
+ else if (m_flForce > 30)
+ {
+ if (vecEst.z > m_vecDesiredPosition.z)
+ m_flForce -= FORCE_NEGDELTA;
+ }
+
+ // pitch forward or back to get to target
+ //-----------------------------------------
+ // Pitch is reversed since Half-Life! (sjb)
+ //-----------------------------------------
+
+
+ // when we're way out, lean forward up to 40 degrees to accelerate to target
+ // not exceeding our goal speed.
+#if 0
+ float nodeSpeed = GetGoalEnt()->m_flSpeed;
+
+ if ( flDist > 300 && flSpeed < m_flGoalSpeed && GetLocalAngles().x + angVel.x < 25 )
+ {
+ angVel.x += 8.0; //lean forward
+ }
+ else if ( flDist < 500 && GetLocalAngles().x + angVel.x > -30 && nodeSpeed == 0)
+ {
+ angVel.x -= 8.0; // lean backwards as we approach the target
+ }
+ else if (GetLocalAngles().x + angVel.x < -20 )
+ {
+ // ALERT( at_console, "f " );
+ angVel.x += 2.0;
+ }
+ else if (GetLocalAngles().x + angVel.x > 20 )
+ {
+ // ALERT( at_console, "b " );
+ angVel.x -= 2.0;
+ }
+#else
+ m_angleVelocity = GetLocalAngles().x + angVel.x;
+ float angleX = angVel.x;
+
+// Msg("AngVel %f, %f\n", m_angleVelocity, flDist);
+ if (flDist > 128 && flSpeed < m_flGoalSpeed && m_angleVelocity < 30)
+ {
+ // ALERT( at_console, "F " );
+ // lean forward
+ angleX += 6;
+ }
+ else if (flDist < -128 && flSpeed > -50 && m_angleVelocity > -20)
+ {
+ // ALERT( at_console, "B " );
+ // lean backward
+ angleX -= 12.0;
+ }
+ else if ( (m_angleVelocity < -20) || (m_angleVelocity < 0 && flDist < 128) )
+ {
+ // ALERT( at_console, "f " );
+ if ( abs(m_angleVelocity) < 5 )
+ {
+ angleX += 1.0;
+ }
+ else
+ {
+ angleX += 4.0;
+ }
+ }
+ else if ( (m_angleVelocity > 20) || (m_angleVelocity > 0 && flDist < 128) )
+ {
+ // ALERT( at_console, "b " );
+ if ( abs(m_angleVelocity) < 5 )
+ {
+ angleX -= 1.0;
+ }
+ else
+ {
+ angleX -= 4.0;
+ }
+ }
+
+ angVel.x = angleX;
+ //Msg("AngVel.x %f %f\n", angVel.x, angleX );
+
+#endif
+
+ SetLocalAngularVelocity( angVel );
+ // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", GetAbsOrigin().x, GetAbsVelocity().x, flDist, flSpeed, GetLocalAngles().x, m_vecAngVelocity.x, m_flForce );
+ // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", GetAbsOrigin().z, GetAbsVelocity().z, vecEst.z, m_vecDesiredPosition.z, m_flForce );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::InitializeRotorSound( void )
+{
+ if (m_pRotorSound)
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ // Get the rotor sound started up.
+ controller.Play( m_pRotorSound, 0.0, 100 );
+ controller.SoundChangeVolume(m_pRotorSound, GetRotorVolume(), 2.0);
+ }
+
+ m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::UpdateRotorSoundPitch( int iPitch )
+{
+ if (m_pRotorSound)
+ {
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundChangePitch( m_pRotorSound, iPitch, 0.1 );
+ controller.SoundChangeVolume( m_pRotorSound, GetRotorVolume(), 0.1 );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::FlyTouch( CBaseEntity *pOther )
+{
+ // bounce if we hit something solid
+ if ( pOther->GetSolid() == SOLID_BSP)
+ {
+ const trace_t &tr = CBaseEntity::GetTouchTrace( );
+
+ // UNDONE, do a real bounce
+ ApplyAbsVelocityImpulse( tr.plane.normal * (GetAbsVelocity().Length() + 200) );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::CrashTouch( CBaseEntity *pOther )
+{
+ // only crash if we hit something solid
+ if ( pOther->GetSolid() == SOLID_BSP)
+ {
+ SetTouch( NULL );
+ SetNextThink( gpGlobals->curtime );
+
+ CPASFilter filter( GetAbsOrigin() );
+ for (int i = 0; i < 5; i++)
+ {
+ Vector pos = GetAbsOrigin();
+
+ pos.x += random->RandomFloat( -150, 150 );
+ pos.y += random->RandomFloat( -150, 150 );
+ pos.z += random->RandomFloat( -150, -50 );
+ te->Explosion( filter, MIN( 0.99, i * 0.2 ), &pos, g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0 );
+ }
+
+ UTIL_Remove( this );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::DyingThink( void )
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ SetLocalAngularVelocity( GetLocalAngularVelocity() * 1.02 );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+int CBaseHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+#if 0
+ // This code didn't port easily. WTF does it do? (sjb)
+ if (pevInflictor->m_owner == pev)
+ return 0;
+#endif
+
+ /*
+ if ( (bitsDamageType & DMG_BULLET) && flDamage > 50)
+ {
+ // clip bullet damage at 50
+ flDamage = 50;
+ }
+ */
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Override base class to add display of fly direction
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CBaseHelicopter::DrawDebugGeometryOverlays(void)
+{
+ if (m_pfnThink!= NULL)
+ {
+ // ------------------------------
+ // Draw route if requested
+ // ------------------------------
+ if (m_debugOverlays & OVERLAY_NPC_ROUTE_BIT)
+ {
+ NDebugOverlay::Line(GetAbsOrigin(), m_vecDesiredPosition, 0,0,255, true, 0);
+ }
+ }
+ BaseClass::DrawDebugGeometryOverlays();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CBaseHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo dmgInfo = info;
+
+ // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage );
+
+ // HITGROUPS don't work currently.
+ // ignore blades
+// if (ptr->hitgroup == 6 && (info.GetDamageType() & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB)))
+// return;
+
+ // hit hard, hits cockpit
+ if (info.GetDamage() > 50 || ptr->hitgroup == 11 )
+ {
+ // ALERT( at_console, "%map .0f\n", flDamage );
+ AddMultiDamage( dmgInfo, this );
+// m_iDoSmokePuff = 3 + (info.GetDamage() / 5.0);
+ }
+ else
+ {
+ // do half damage in the body
+ dmgInfo.ScaleDamage(0.5);
+ AddMultiDamage( dmgInfo, this );
+ g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal );
+ }
+
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CBaseHelicopter::NullThink( void )
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.5f );
+}
+
+
+void CBaseHelicopter::Startup( void )
+{
+ m_flGoalSpeed = m_flInitialSpeed;
+ SetThink( &CBaseHelicopter::HelicopterThink );
+ SetTouch( &CBaseHelicopter::FlyTouch );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+void CBaseHelicopter::Event_Killed( const CTakeDamageInfo &info )
+{
+ m_lifeState = LIFE_DYING;
+
+ SetMoveType( MOVETYPE_FLYGRAVITY );
+ SetGravity( UTIL_ScaleForGravity( 240 ) ); // use a lower gravity
+
+ // Kill the rotor sound.
+
+ UTIL_SetSize( this, Vector( -32, -32, -64), Vector( 32, 32, 0) );
+ SetThink( &CBaseHelicopter::CallDyingThink );
+ SetTouch( &CBaseHelicopter::CrashTouch );
+
+ m_flNextCrashExplosion = gpGlobals->curtime + 0.0f;
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ m_iHealth = 0;
+ m_takedamage = DAMAGE_NO;
+
+/*
+ if (m_spawnflags & SF_NOWRECKAGE)
+ {
+ m_flNextRocket = gpGlobals->curtime + 4.0;
+ }
+ else
+ {
+ m_flNextRocket = gpGlobals->curtime + 15.0;
+ }
+*/
+ m_OnDeath.FireOutput( info.GetAttacker(), this );
+}
+
+
+void CBaseHelicopter::StopLoopingSounds()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ controller.SoundDestroy( m_pRotorSound );
+ m_pRotorSound = NULL;
+
+ BaseClass::StopLoopingSounds();
+}
+
+void CBaseHelicopter::GibMonster( void )
+{
+}
+
+
+void CBaseHelicopter::ChangePathCorner( const char *pszName )
+{
+ if( m_lifeState != LIFE_ALIVE )
+ {
+ // Disregard this if dead or dying.
+ return;
+ }
+
+ if (GetGoalEnt())
+ {
+ SetGoalEnt( gEntList.FindEntityByName( NULL, pszName ) );
+
+ // I don't think we need to do this. The FLIGHT() code will do it for us (sjb)
+ if (GetGoalEnt())
+ {
+ m_vecDesiredPosition = GetGoalEnt()->GetLocalOrigin();
+ AngleVectors( GetGoalEnt()->GetLocalAngles(), &m_vecGoalOrientation );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Slams the chopper's current path corner and sends it to a new one.
+// This code does NOT check that the path from the current position
+// to the wished path corner is clear!
+//-----------------------------------------------------------------------------
+void CBaseHelicopter::InputChangePathCorner( inputdata_t &inputdata )
+{
+ ChangePathCorner( inputdata.value.String() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Call Startup for a helicopter that's been flagged to start disabled
+//-----------------------------------------------------------------------------
+void CBaseHelicopter::InputActivate( inputdata_t &inputdata )
+{
+ if( m_spawnflags & SF_AWAITINPUT )
+ {
+ Startup();
+
+ // Now clear the spawnflag to protect from
+ // subsequent calls.
+ m_spawnflags &= ~SF_AWAITINPUT;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+void CBaseHelicopter::ApplySidewaysDrag( const Vector &vecRight )
+{
+ Vector vecNewVelocity = GetAbsVelocity();
+ vecNewVelocity.x *= 1.0 - fabs( vecRight.x ) * 0.05;
+ vecNewVelocity.y *= 1.0 - fabs( vecRight.y ) * 0.05;
+ vecNewVelocity.z *= 1.0 - fabs( vecRight.z ) * 0.05;
+ SetAbsVelocity( vecNewVelocity );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CBaseHelicopter::ApplyGeneralDrag( void )
+{
+ Vector vecNewVelocity = GetAbsVelocity();
+ vecNewVelocity *= 0.995;
+ SetAbsVelocity( vecNewVelocity );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+bool CBaseHelicopter::ChooseEnemy( void )
+{
+ // See if there's a new enemy.
+ CBaseEntity *pNewEnemy;
+
+ pNewEnemy = BestEnemy();
+
+ if( ( pNewEnemy != GetEnemy() ) && pNewEnemy != NULL )
+ {
+ //New enemy! Clear the timers and set conditions.
+ SetEnemy( pNewEnemy );
+ m_flLastSeen = m_flPrevSeen = gpGlobals->curtime;
+ return true;
+ }
+ else
+ {
+ ClearCondition( COND_NEW_ENEMY );
+ return false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CBaseHelicopter::CheckEnemy( CBaseEntity *pEnemy )
+{
+ // -------------------
+ // If enemy is dead
+ // -------------------
+ if ( !pEnemy->IsAlive() )
+ {
+ SetCondition( COND_ENEMY_DEAD );
+ ClearCondition( COND_SEE_ENEMY );
+ ClearCondition( COND_ENEMY_OCCLUDED );
+ return;
+ }
+}
+
+bool CBaseHelicopter::HasReachedTarget( void )
+{
+ float flDist = (WorldSpaceCenter() - m_vecDesiredPosition).Length();
+
+ if( GetGoalEnt()->m_flSpeed <= 0 )
+ return ( flDist < 145 );
+ else
+ return( flDist < 512 );
+}
+
diff --git a/game/server/hl1/hl1_npc_roach.cpp b/game/server/hl1/hl1_npc_roach.cpp
new file mode 100644
index 0000000..d5a4be7
--- /dev/null
+++ b/game/server/hl1/hl1_npc_roach.cpp
@@ -0,0 +1,471 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "hl1_ai_basenpc.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 "ai_behavior_follow.h"
+#include "ai_navigator.h"
+#include "decals.h"
+
+
+#define ROACH_IDLE 0
+#define ROACH_BORED 1
+#define ROACH_SCARED_BY_ENT 2
+#define ROACH_SCARED_BY_LIGHT 3
+#define ROACH_SMELL_FOOD 4
+#define ROACH_EAT 5
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+class CNPC_Roach : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Roach, CHL1BaseNPC );
+
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ float MaxYawSpeed( void );
+
+// DECLARE_DATADESC();
+
+ void NPCThink ( void );
+ void PickNewDest ( int iCondition );
+ void Look ( int iDistance );
+ void Move ( float flInterval );
+
+ Class_T Classify( void ) { return CLASS_INSECT; }
+
+ void Touch ( CBaseEntity *pOther );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+ int GetSoundInterests ( void );
+
+ void Eat( float flFullDuration );
+ bool ShouldEat( void );
+
+ bool ShouldGib( const CTakeDamageInfo &info ) { return false; }
+
+ float m_flLastLightLevel;
+ float m_flNextSmellTime;
+
+ // UNDONE: These don't necessarily need to be save/restored, but if we add more data, it may
+ bool m_fLightHacked;
+ int m_iMode;
+
+ float m_flHungryTime;
+ // -----------------------------
+};
+
+LINK_ENTITY_TO_CLASS( monster_cockroach, CNPC_Roach );
+
+//BEGIN_DATADESC( CNPC_Roach )
+
+// DEFINE_FUNCTION( RoachTouch ),
+
+//END_DATADESC()
+
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Roach::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/roach.mdl" );
+ UTIL_SetSize( this, Vector( -1, -1, 0 ), Vector( 1, 1, 2 ) );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_YELLOW;
+ ClearEffects();
+ m_iHealth = 1;
+ m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ NPCInit();
+ SetActivity ( ACT_IDLE );
+
+ SetViewOffset ( Vector ( 0, 0, 1 ) );// position of the eyes relative to monster's origin.
+ m_takedamage = DAMAGE_YES;
+ m_fLightHacked = FALSE;
+ m_flLastLightLevel = -1;
+ m_iMode = ROACH_IDLE;
+ m_flNextSmellTime = gpGlobals->curtime;
+
+ AddEffects( EF_NOSHADOW );
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Roach::Precache()
+{
+ PrecacheModel("models/roach.mdl");
+
+ PrecacheScriptSound( "Roach.Walk" );
+ PrecacheScriptSound( "Roach.Die" );
+ PrecacheScriptSound( "Roach.Smash" );
+}
+
+float CNPC_Roach::MaxYawSpeed( void )
+{
+ return 120.0f;
+}
+
+void CNPC_Roach::Eat( float flFullDuration )
+{
+ m_flHungryTime = gpGlobals->curtime + flFullDuration;
+}
+
+bool CNPC_Roach::ShouldEat( void )
+{
+ if ( m_flHungryTime > gpGlobals->curtime )
+ {
+ return false;
+ }
+
+ return true;
+}
+
+//=========================================================
+// MonsterThink, overridden for roaches.
+//=========================================================
+void CNPC_Roach::NPCThink( void )
+{
+ if ( FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 1.0f , 1.5f ) );
+ else
+ SetNextThink( gpGlobals->curtime + 0.1f );// keep monster thinking
+
+ float flInterval = gpGlobals->curtime - GetLastThink();
+
+ StudioFrameAdvance( ); // animate
+
+ if ( !m_fLightHacked )
+ {
+ // if light value hasn't been collection for the first time yet,
+ // suspend the creature for a second so the world finishes spawning, then we'll collect the light level.
+ SetNextThink( gpGlobals->curtime + 1 );
+ m_fLightHacked = TRUE;
+ return;
+ }
+ else if ( m_flLastLightLevel < 0 )
+ {
+ // collect light level for the first time, now that all of the lightmaps in the roach's area have been calculated.
+ m_flLastLightLevel = 0;
+ }
+
+ switch ( m_iMode )
+ {
+ case ROACH_IDLE:
+ case ROACH_EAT:
+ {
+ // if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random.
+ if ( random->RandomInt( 0, 3 ) == 1 )
+ {
+ Look( 150 );
+
+ if ( HasCondition( COND_SEE_FEAR ) )
+ {
+ // if see something scary
+ //ALERT ( at_aiconsole, "Scared\n" );
+ Eat( 30 + ( random->RandomInt( 0, 14 ) ) );// roach will ignore food for 30 to 45 seconds
+ PickNewDest( ROACH_SCARED_BY_ENT );
+ SetActivity ( ACT_WALK );
+ }
+ else if ( random->RandomInt( 0,149 ) == 1 )
+ {
+ // if roach doesn't see anything, there's still a chance that it will move. (boredom)
+ //ALERT ( at_aiconsole, "Bored\n" );
+ PickNewDest( ROACH_BORED );
+ SetActivity ( ACT_WALK );
+
+ if ( m_iMode == ROACH_EAT )
+ {
+ // roach will ignore food for 30 to 45 seconds if it got bored while eating.
+ Eat( 30 + ( random->RandomInt(0,14) ) );
+ }
+ }
+ }
+
+ // don't do this stuff if eating!
+ if ( m_iMode == ROACH_IDLE )
+ {
+ if ( ShouldEat() )
+ {
+ GetSenses()->Listen();
+ }
+
+ if ( 0 > m_flLastLightLevel )
+ {
+ // someone turned on lights!
+ //ALERT ( at_console, "Lights!\n" );
+ PickNewDest( ROACH_SCARED_BY_LIGHT );
+ SetActivity ( ACT_WALK );
+ }
+ else if ( HasCondition( COND_SMELL ) )
+ {
+ CSound *pSound = GetLoudestSoundOfType( ALL_SOUNDS );
+
+ // roach smells food and is just standing around. Go to food unless food isn't on same z-plane.
+ if ( pSound && abs( pSound->GetSoundOrigin().z - GetAbsOrigin().z ) <= 3 )
+ {
+ PickNewDest( ROACH_SMELL_FOOD );
+ SetActivity ( ACT_WALK );
+ }
+ }
+ }
+
+ break;
+ }
+ case ROACH_SCARED_BY_LIGHT:
+ {
+ // if roach was scared by light, then stop if we're over a spot at least as dark as where we started!
+ if ( 0 <= m_flLastLightLevel )
+ {
+ SetActivity ( ACT_IDLE );
+ m_flLastLightLevel = 0;// make this our new light level.
+ }
+ break;
+ }
+ }
+
+ if ( GetActivity() != ACT_IDLE )
+ {
+ Move( flInterval );
+ }
+}
+
+void CNPC_Roach::PickNewDest ( int iCondition )
+{
+ Vector vecNewDir;
+ Vector vecDest;
+ float flDist;
+
+ m_iMode = iCondition;
+
+ GetNavigator()->ClearGoal();
+
+ if ( m_iMode == ROACH_SMELL_FOOD )
+ {
+ // find the food and go there.
+ CSound *pSound = GetLoudestSoundOfType( ALL_SOUNDS );
+
+ if ( pSound )
+ {
+ GetNavigator()->SetRandomGoal( 3 - random->RandomInt( 0,5 ) );
+ return;
+ }
+ }
+
+ do
+ {
+ // picks a random spot, requiring that it be at least 128 units away
+ // else, the roach will pick a spot too close to itself and run in
+ // circles. this is a hack but buys me time to work on the real monsters.
+ vecNewDir.x = random->RandomInt( -1, 1 );
+ vecNewDir.y = random->RandomInt( -1, 1 );
+ flDist = 256 + ( random->RandomInt(0,255) );
+ vecDest = GetAbsOrigin() + vecNewDir * flDist;
+
+ } while ( ( vecDest - GetAbsOrigin() ).Length2D() < 128 );
+
+ Vector vecLocation;
+
+ vecLocation.x = vecDest.x;
+ vecLocation.y = vecDest.y;
+ vecLocation.z = GetAbsOrigin().z;
+
+ AI_NavGoal_t goal( GOALTYPE_LOCATION, vecLocation, ACT_WALK );
+
+ GetNavigator()->SetGoal( goal );
+
+ if ( random->RandomInt( 0, 9 ) == 1 )
+ {
+ // every once in a while, a roach will play a skitter sound when they decide to run
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Roach.Walk" );
+ }
+}
+
+//=========================================================
+// Look - overriden for the roach, which can virtually see
+// 360 degrees.
+//=========================================================
+void CNPC_Roach::Look ( int iDistance )
+{
+ CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with
+
+ // DON'T let visibility information from last frame sit around!
+ ClearCondition( COND_SEE_HATE | COND_SEE_DISLIKE | COND_SEE_ENEMY | COND_SEE_FEAR );
+
+ // don't let monsters outside of the player's PVS act up, or most of the interesting
+ // things will happen before the player gets there!
+ if ( FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
+ {
+ return;
+ }
+
+ // Does sphere also limit itself to PVS?
+ // Examine all entities within a reasonable radius
+ // !!!PERFORMANCE - let's trivially reject the ent list before radius searching!
+
+ for ( CEntitySphereQuery sphere( GetAbsOrigin(), iDistance ); ( pSightEnt = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
+ {
+ // only consider ents that can be damaged. !!!temporarily only considering other monsters and clients
+ if ( pSightEnt->IsPlayer() || FBitSet ( pSightEnt->GetFlags(), FL_NPC ) )
+ {
+ if ( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->GetFlags(), FL_NOTARGET ) && pSightEnt->m_iHealth > 0 )
+ {
+ // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when
+ // we see monsters other than the Enemy.
+ switch ( IRelationType ( pSightEnt ) )
+ {
+ case D_FR:
+ SetCondition( COND_SEE_FEAR );
+ break;
+ case D_NU:
+ break;
+ default:
+ Msg ( "%s can't asses %s\n", GetClassname(), pSightEnt->GetClassname() );
+ break;
+ }
+ }
+ }
+ }
+}
+
+//=========================================================
+// roach's move function
+//=========================================================
+void CNPC_Roach::Move ( float flInterval )
+{
+ float flWaypointDist;
+ Vector vecApex;
+
+ // local move to waypoint.
+ flWaypointDist = ( GetNavigator()->GetGoalPos() - GetAbsOrigin() ).Length2D();
+
+ GetMotor()->SetIdealYawToTargetAndUpdate( GetNavigator()->GetGoalPos() );
+
+ float speed = 150 * flInterval;
+
+ Vector vToTarget = GetNavigator()->GetGoalPos() - GetAbsOrigin();
+ vToTarget.NormalizeInPlace();
+ Vector vMovePos = vToTarget * speed;
+
+ if ( random->RandomInt( 0,7 ) == 1 )
+ {
+ // randomly change direction
+ PickNewDest( m_iMode );
+ }
+
+ if( !WalkMove( vMovePos, MASK_NPCSOLID ) )
+ {
+ PickNewDest( m_iMode );
+ }
+
+ // if the waypoint is closer than step size, then stop after next step (ok for roach to overshoot)
+ if ( flWaypointDist <= m_flGroundSpeed * flInterval )
+ {
+ // take truncated step and stop
+
+ SetActivity ( ACT_IDLE );
+ m_flLastLightLevel = 0;// this is roach's new comfortable light level
+
+ if ( m_iMode == ROACH_SMELL_FOOD )
+ {
+ m_iMode = ROACH_EAT;
+ }
+ else
+ {
+ m_iMode = ROACH_IDLE;
+ }
+ }
+
+ if ( random->RandomInt( 0,149 ) == 1 && m_iMode != ROACH_SCARED_BY_LIGHT && m_iMode != ROACH_SMELL_FOOD )
+ {
+ // random skitter while moving as long as not on a b-line to get out of light or going to food
+ PickNewDest( FALSE );
+ }
+}
+
+void CNPC_Roach::Touch ( CBaseEntity *pOther )
+{
+ Vector vecSpot;
+ trace_t tr;
+
+ if ( pOther->GetAbsVelocity() == vec3_origin || !pOther->IsPlayer() )
+ {
+ return;
+ }
+
+ vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );//move up a bit, and trace down.
+ //UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr);
+
+ UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), MASK_ALL, this, COLLISION_GROUP_NONE, &tr);
+
+ // This isn't really blood. So you don't have to screen it out based on violence levels (UTIL_ShouldShowBlood())
+ UTIL_DecalTrace( &tr, "YellowBlood" );
+
+ // DMG_GENERIC because we don't want any physics force generated
+ TakeDamage( CTakeDamageInfo( pOther, pOther, m_iHealth, DMG_GENERIC ) );
+}
+
+void CNPC_Roach::Event_Killed( const CTakeDamageInfo &info )
+{
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ CPASAttenuationFilter filter( this );
+
+ //random sound
+ if ( random->RandomInt( 0,4 ) == 1 )
+ {
+ EmitSound( filter, entindex(), "Roach.Die" );
+ }
+ else
+ {
+ EmitSound( filter, entindex(), "Roach.Smash" );
+ }
+
+ CSoundEnt::InsertSound ( SOUND_WORLD, GetAbsOrigin(), 128, 1 );
+
+ UTIL_Remove( this );
+}
+
+int CNPC_Roach::GetSoundInterests ( void)
+{
+ return SOUND_CARCASS |
+ SOUND_MEAT;
+}
diff --git a/game/server/hl1/hl1_npc_scientist.cpp b/game/server/hl1/hl1_npc_scientist.cpp
new file mode 100644
index 0000000..d4f0bd9
--- /dev/null
+++ b/game/server/hl1/hl1_npc_scientist.cpp
@@ -0,0 +1,1410 @@
+//========= 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 "hl1_npc_scientist.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "animation.h"
+#include "engine/IEngineSound.h"
+#include "ai_navigator.h"
+#include "ai_behavior_follow.h"
+#include "AI_Criteria.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+
+#define SC_PLFEAR "SC_PLFEAR"
+#define SC_FEAR "SC_FEAR"
+#define SC_HEAL "SC_HEAL"
+#define SC_SCREAM "SC_SCREAM"
+#define SC_POK "SC_POK"
+
+ConVar sk_scientist_health( "sk_scientist_health","20");
+ConVar sk_scientist_heal( "sk_scientist_heal","25");
+
+#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model
+enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 };
+
+
+int ACT_EXCITED;
+
+//=========================================================
+// Makes it fast to check barnacle classnames in
+// IsValidEnemy()
+//=========================================================
+string_t s_iszBarnacleClassname;
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define SCIENTIST_AE_HEAL ( 1 )
+#define SCIENTIST_AE_NEEDLEON ( 2 )
+#define SCIENTIST_AE_NEEDLEOFF ( 3 )
+
+//=======================================================
+// Scientist
+//=======================================================
+
+LINK_ENTITY_TO_CLASS( monster_scientist, CNPC_Scientist );
+
+//IMPLEMENT_SERVERCLASS_ST( CNPC_Scientist, DT_NPC_Scientist )
+//END_SEND_TABLE()
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_Scientist )
+ DEFINE_FIELD( m_flFearTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flHealTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flPainTime, FIELD_TIME ),
+
+ DEFINE_THINKFUNC( SUB_LVFadeOut ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CNPC_Scientist::Precache( void )
+{
+ PrecacheModel( "models/scientist.mdl" );
+
+ PrecacheScriptSound( "Scientist.Pain" );
+
+ TalkInit();
+
+ BaseClass::Precache();
+}
+
+void CNPC_Scientist::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet )
+{
+ BaseClass::ModifyOrAppendCriteria( criteriaSet );
+
+ bool predisaster = FBitSet( m_spawnflags, SF_NPC_PREDISASTER ) ? true : false;
+
+ criteriaSet.AppendCriteria( "disaster", predisaster ? "[disaster::pre]" : "[disaster::post]" );
+}
+
+// Init talk data
+void CNPC_Scientist::TalkInit()
+{
+
+ BaseClass::TalkInit();
+
+ // scientist will try to talk to friends in this order:
+
+ m_szFriends[0] = "monster_scientist";
+ m_szFriends[1] = "monster_sitting_scientist";
+ m_szFriends[2] = "monster_barney";
+
+ // get voice for head
+ switch (m_nBody % 3)
+ {
+ default:
+ case HEAD_GLASSES: GetExpresser()->SetVoicePitch( 105 ); break; //glasses
+ case HEAD_EINSTEIN: GetExpresser()->SetVoicePitch( 100 ); break; //einstein
+ case HEAD_LUTHER: GetExpresser()->SetVoicePitch( 95 ); break; //luther
+ case HEAD_SLICK: GetExpresser()->SetVoicePitch( 100 ); break;//slick
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+void CNPC_Scientist::Spawn( void )
+{
+
+ //Select the body first if it's going to be random cause we set his voice pitch in Precache.
+ if ( m_nBody == -1 )
+ m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1 );// pick a head, any head
+
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ Precache();
+
+ SetModel( "models/scientist.mdl" );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_RED;
+ ClearEffects();
+ m_iHealth = sk_scientist_health.GetFloat();
+ m_flFieldOfView = VIEW_FIELD_WIDE;
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE );
+ CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE );
+
+ // White hands
+ m_nSkin = 0;
+
+
+ // Luther is black, make his hands black
+ if ( m_nBody == HEAD_LUTHER )
+ m_nSkin = 1;
+
+ NPCInit();
+
+ SetUse( &CNPC_Scientist::FollowerUse );
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Scientist::Activate()
+{
+ s_iszBarnacleClassname = FindPooledString( "monster_barnacle" );
+ BaseClass::Activate();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+// Output :
+//-----------------------------------------------------------------------------
+Class_T CNPC_Scientist::Classify( void )
+{
+ return CLASS_HUMAN_PASSIVE;
+}
+
+int CNPC_Scientist::GetSoundInterests ( void )
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_DANGER |
+ SOUND_PLAYER;
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Scientist::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case SCIENTIST_AE_HEAL: // Heal my target (if within range)
+ Heal();
+ break;
+ case SCIENTIST_AE_NEEDLEON:
+ {
+ int oldBody = m_nBody;
+ m_nBody = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1;
+ }
+ break;
+ case SCIENTIST_AE_NEEDLEOFF:
+ {
+ int oldBody = m_nBody;
+ m_nBody = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0;
+ }
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+void CNPC_Scientist::DeclineFollowing( void )
+{
+ if ( CanSpeakAfterMyself() )
+ {
+ Speak( SC_POK );
+ }
+}
+
+bool CNPC_Scientist::CanBecomeRagdoll( void )
+{
+ if ( UTIL_IsLowViolence() )
+ {
+ return false;
+ }
+
+ return BaseClass::CanBecomeRagdoll();
+}
+
+bool CNPC_Scientist::ShouldGib( const CTakeDamageInfo &info )
+{
+ if ( UTIL_IsLowViolence() )
+ {
+ return false;
+ }
+
+ return BaseClass::ShouldGib( info );
+}
+
+void CNPC_Scientist::SUB_StartLVFadeOut( float delay, bool notSolid )
+{
+ SetThink( &CNPC_Scientist::SUB_LVFadeOut );
+ SetNextThink( gpGlobals->curtime + delay );
+ SetRenderColorA( 255 );
+ m_nRenderMode = kRenderNormal;
+
+ if ( notSolid )
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetLocalAngularVelocity( vec3_angle );
+ }
+}
+
+void CNPC_Scientist::SUB_LVFadeOut( void )
+{
+ if( VPhysicsGetObject() )
+ {
+ if( VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD || GetEFlags() & EFL_IS_BEING_LIFTED_BY_BARNACLE )
+ {
+ // Try again in a few seconds.
+ SetNextThink( gpGlobals->curtime + 5 );
+ SetRenderColorA( 255 );
+ return;
+ }
+ }
+
+ float dt = gpGlobals->frametime;
+ if ( dt > 0.1f )
+ {
+ dt = 0.1f;
+ }
+ m_nRenderMode = kRenderTransTexture;
+ int speed = MAX(3,256*dt); // fade out over 3 seconds
+ SetRenderColorA( UTIL_Approach( 0, m_clrRender->a, speed ) );
+ NetworkStateChanged();
+
+ if ( m_clrRender->a == 0 )
+ {
+ UTIL_Remove(this);
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime );
+ }
+}
+
+void CNPC_Scientist::Scream( void )
+{
+ if ( IsOkToSpeak() )
+ {
+ GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 10 );
+ SetSpeechTarget( GetEnemy() );
+ Speak( SC_SCREAM );
+ }
+}
+
+Activity CNPC_Scientist::GetStoppedActivity( void )
+{
+ if ( GetEnemy() != NULL )
+ return (Activity)ACT_EXCITED;
+
+ return BaseClass::GetStoppedActivity();
+}
+
+float CNPC_Scientist::MaxYawSpeed( void )
+{
+ switch( GetActivity() )
+ {
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 160;
+ break;
+ case ACT_RUN:
+ return 160;
+ break;
+ default:
+ return 60;
+ break;
+ }
+}
+
+void CNPC_Scientist::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_SAY_HEAL:
+
+ GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 2 );
+ SetSpeechTarget( GetTarget() );
+ Speak( SC_HEAL );
+
+ TaskComplete();
+ break;
+
+ case TASK_SCREAM:
+ Scream();
+ TaskComplete();
+ break;
+
+ case TASK_RANDOM_SCREAM:
+ if ( random->RandomFloat( 0, 1 ) < pTask->flTaskData )
+ Scream();
+ TaskComplete();
+ break;
+
+ case TASK_SAY_FEAR:
+ if ( IsOkToSpeak() )
+ {
+ GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 2 );
+ SetSpeechTarget( GetEnemy() );
+ if ( GetEnemy() && GetEnemy()->IsPlayer() )
+ Speak( SC_PLFEAR );
+ else
+ Speak( SC_FEAR );
+ }
+ TaskComplete();
+ break;
+
+ case TASK_HEAL:
+ SetIdealActivity( ACT_MELEE_ATTACK1 );
+ break;
+
+ case TASK_RUN_PATH_SCARED:
+ GetNavigator()->SetMovementActivity( ACT_RUN_SCARED );
+ break;
+
+ case TASK_MOVE_TO_TARGET_RANGE_SCARED:
+ {
+ if ( GetTarget() == NULL)
+ {
+ TaskFail(FAIL_NO_TARGET);
+ }
+ else if ( (GetTarget()->GetAbsOrigin() - GetAbsOrigin()).Length() < 1 )
+ {
+ TaskComplete();
+ }
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+void CNPC_Scientist::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_RUN_PATH_SCARED:
+ if ( !IsMoving() )
+ TaskComplete();
+ if ( random->RandomInt(0,31) < 8 )
+ Scream();
+ break;
+
+ case TASK_MOVE_TO_TARGET_RANGE_SCARED:
+ {
+ float distance;
+
+ if ( GetTarget() == NULL )
+ {
+ TaskFail(FAIL_NO_TARGET);
+ }
+ else
+ {
+ distance = ( GetNavigator()->GetPath()->ActualGoalPosition() - GetAbsOrigin() ).Length2D();
+ // Re-evaluate when you think your finished, or the target has moved too far
+ if ( (distance < pTask->flTaskData) || (GetNavigator()->GetPath()->ActualGoalPosition() - GetTarget()->GetAbsOrigin()).Length() > pTask->flTaskData * 0.5 )
+ {
+ GetNavigator()->GetPath()->ResetGoalPosition(GetTarget()->GetAbsOrigin());
+ distance = ( GetNavigator()->GetPath()->ActualGoalPosition() - GetAbsOrigin() ).Length2D();
+// GetNavigator()->GetPath()->Find();
+ GetNavigator()->SetGoal( GOALTYPE_TARGETENT );
+ }
+
+ // Set the appropriate activity based on an overlapping range
+ // overlap the range to prevent oscillation
+ // BUGBUG: this is checking linear distance (ie. through walls) and not path distance or even visibility
+ if ( distance < pTask->flTaskData )
+ {
+ TaskComplete();
+ GetNavigator()->GetPath()->Clear(); // Stop moving
+ }
+ else
+ {
+ if ( distance < 190 && GetNavigator()->GetMovementActivity() != ACT_WALK_SCARED )
+ GetNavigator()->SetMovementActivity( ACT_WALK_SCARED );
+ else if ( distance >= 270 && GetNavigator()->GetMovementActivity() != ACT_RUN_SCARED )
+ GetNavigator()->SetMovementActivity( ACT_RUN_SCARED );
+ }
+ }
+ }
+ break;
+
+ case TASK_HEAL:
+ if ( IsSequenceFinished() )
+ {
+ TaskComplete();
+ }
+ else
+ {
+ if ( TargetDistance() > 90 )
+ TaskComplete();
+
+ if ( GetTarget() )
+ GetMotor()->SetIdealYaw( UTIL_VecToYaw( GetTarget()->GetAbsOrigin() - GetAbsOrigin() ) );
+
+ //GetMotor()->SetYawSpeed( m_YawSpeed );
+ }
+ break;
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+int CNPC_Scientist::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+
+ if ( inputInfo.GetInflictor() && inputInfo.GetInflictor()->GetFlags() & FL_CLIENT )
+ {
+ Remember( bits_MEMORY_PROVOKED );
+ StopFollowing();
+ }
+
+ // make sure friends talk about it if player hurts scientist...
+ return BaseClass::OnTakeDamage_Alive( inputInfo );
+}
+
+void CNPC_Scientist::Event_Killed( const CTakeDamageInfo &info )
+{
+ SetUse( NULL );
+ BaseClass::Event_Killed( info );
+
+ if ( UTIL_IsLowViolence() )
+ {
+ SUB_StartLVFadeOut( 0.0f );
+ }
+}
+
+bool CNPC_Scientist::CanHeal( void )
+{
+ CBaseEntity *pTarget = GetFollowTarget();
+
+ if ( pTarget == NULL )
+ return false;
+
+ if ( pTarget->IsPlayer() == false )
+ return false;
+
+ if ( (m_flHealTime > gpGlobals->curtime) || (pTarget->m_iHealth > (pTarget->m_iMaxHealth * 0.5)) )
+ return false;
+
+ return true;
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_Scientist::PainSound ( const CTakeDamageInfo &info )
+{
+ if (gpGlobals->curtime < m_flPainTime )
+ return;
+
+ m_flPainTime = gpGlobals->curtime + random->RandomFloat( 0.5, 0.75 );
+
+ CPASAttenuationFilter filter( this );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "Scientist.Pain", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ params.pitch = GetExpresser()->GetVoicePitch();
+
+ EmitSound( filter, entindex(), ep );
+ }
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_Scientist::DeathSound( const CTakeDamageInfo &info )
+{
+ PainSound( info );
+}
+
+
+void CNPC_Scientist::Heal( void )
+{
+ if ( !CanHeal() )
+ return;
+
+ Vector target = GetFollowTarget()->GetAbsOrigin() - GetAbsOrigin();
+ if ( target.Length() > 100 )
+ return;
+
+ GetTarget()->TakeHealth( sk_scientist_heal.GetFloat(), DMG_GENERIC );
+ // Don't heal again for 1 minute
+ m_flHealTime = gpGlobals->curtime + 60;
+}
+
+int CNPC_Scientist::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ // Hook these to make a looping schedule
+ case SCHED_TARGET_FACE:
+ {
+ int baseType;
+
+ // call base class default so that scientist will talk
+ // when 'used'
+ baseType = BaseClass::TranslateSchedule( scheduleType );
+
+ if (baseType == SCHED_IDLE_STAND)
+ return SCHED_TARGET_FACE; // override this for different target face behavior
+ else
+ return baseType;
+ }
+ break;
+
+ case SCHED_TARGET_CHASE:
+ return SCHED_SCI_FOLLOWTARGET;
+ break;
+
+ case SCHED_IDLE_STAND:
+ {
+ int baseType;
+
+ baseType = BaseClass::TranslateSchedule( scheduleType );
+
+ if (baseType == SCHED_IDLE_STAND)
+ return SCHED_SCI_IDLESTAND; // override this for different target face behavior
+ else
+ return baseType;
+ }
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+Activity CNPC_Scientist::NPC_TranslateActivity( Activity newActivity )
+{
+ if ( GetFollowTarget() && GetEnemy() )
+ {
+ CBaseEntity *pEnemy = GetEnemy();
+
+ int relationship = D_NU;
+
+ // Nothing scary, just me and the player
+ if ( pEnemy != NULL )
+ relationship = IRelationType( pEnemy );
+
+ if ( relationship == D_HT || relationship == D_FR )
+ {
+ if ( newActivity == ACT_WALK )
+ return ACT_WALK_SCARED;
+ else if ( newActivity == ACT_RUN )
+ return ACT_RUN_SCARED;
+ }
+ }
+
+ return BaseClass::NPC_TranslateActivity( newActivity );
+}
+
+int CNPC_Scientist::SelectSchedule( void )
+{
+ if( m_NPCState == NPC_STATE_PRONE )
+ {
+ // Immediately call up to the talker code. Barnacle death is priority schedule.
+ return BaseClass::SelectSchedule();
+ }
+
+ // so we don't keep calling through the EHANDLE stuff
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if ( GetFollowTarget() )
+ {
+ // so we don't keep calling through the EHANDLE stuff
+ CBaseEntity *pEnemy = GetEnemy();
+
+ int relationship = D_NU;
+
+ // Nothing scary, just me and the player
+ if ( pEnemy != NULL )
+ relationship = IRelationType( pEnemy );
+
+ if ( relationship != D_HT && relationship != D_FR )
+ {
+ // If I'm already close enough to my target
+ if ( TargetDistance() <= 128 )
+ {
+ if ( CanHeal() ) // Heal opportunistically
+ {
+ SetTarget( GetFollowTarget() );
+ return SCHED_SCI_HEAL;
+ }
+ }
+ }
+ }
+ else if ( HasCondition( COND_PLAYER_PUSHING ) && !(GetSpawnFlags() & SF_NPC_PREDISASTER ) )
+ { // Player wants me to move
+ return SCHED_HL1TALKER_FOLLOW_MOVE_AWAY;
+ }
+
+ if ( BehaviorSelectSchedule() )
+ {
+ return BaseClass::SelectSchedule();
+ }
+
+
+
+ if ( HasCondition( COND_HEAR_DANGER ) && m_NPCState != NPC_STATE_PRONE )
+ {
+ CSound *pSound;
+ pSound = GetBestSound();
+
+ if ( pSound && pSound->IsSoundType(SOUND_DANGER) )
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND;
+ }
+
+ switch( m_NPCState )
+ {
+
+ case NPC_STATE_ALERT:
+ case NPC_STATE_IDLE:
+
+ if ( pEnemy )
+ {
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ m_flFearTime = gpGlobals->curtime;
+ else if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert
+ {
+ SetEnemy( NULL );
+ pEnemy = NULL;
+ }
+ }
+
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ // flinch if hurt
+ return SCHED_SMALL_FLINCH;
+ }
+
+ // Cower when you hear something scary
+ if ( HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_COMBAT ) )
+ {
+ CSound *pSound;
+ pSound = GetBestSound();
+
+ if ( pSound )
+ {
+ if ( pSound->IsSoundType(SOUND_DANGER | SOUND_COMBAT) )
+ {
+ if ( gpGlobals->curtime - m_flFearTime > 3 ) // Only cower every 3 seconds or so
+ {
+ m_flFearTime = gpGlobals->curtime; // Update last fear
+ return SCHED_SCI_STARTLE; // This will just duck for a second
+ }
+ }
+ }
+ }
+
+ if ( GetFollowTarget() )
+ {
+ if ( !GetFollowTarget()->IsAlive() )
+ {
+ // UNDONE: Comment about the recently dead player here?
+ StopFollowing();
+ break;
+ }
+
+ int relationship = D_NU;
+
+ // Nothing scary, just me and the player
+ if ( pEnemy != NULL )
+ relationship = IRelationType( pEnemy );
+
+ if ( relationship != D_HT )
+ {
+ return SCHED_TARGET_FACE; // Just face and follow.
+ }
+ else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared
+ {
+ if ( HasCondition( COND_NEW_ENEMY ) ) // I just saw something new and scary, react
+ return SCHED_SCI_FEAR; // React to something scary
+ return SCHED_SCI_FACETARGETSCARED; // face and follow, but I'm scared!
+ }
+ }
+
+ // try to say something about smells
+ TrySmellTalk();
+ break;
+
+
+ case NPC_STATE_COMBAT:
+
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ return SCHED_SCI_FEAR; // Point and scream!
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ return SCHED_SCI_COVER; // Take Cover
+
+ if ( HasCondition( COND_HEAR_COMBAT ) || HasCondition( COND_HEAR_DANGER ) )
+ return SCHED_TAKE_COVER_FROM_BEST_SOUND; // Cower and panic from the scary sound!
+
+ return SCHED_SCI_COVER; // Run & Cower
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+NPC_STATE CNPC_Scientist::SelectIdealState ( void )
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_ALERT:
+ case NPC_STATE_IDLE:
+ if ( HasCondition( COND_NEW_ENEMY ) )
+ {
+ if ( GetFollowTarget() && GetEnemy() )
+ {
+ int relationship = IRelationType( GetEnemy() );
+ if ( relationship != D_FR || relationship != D_HT && ( !HasCondition( COND_LIGHT_DAMAGE ) || !HasCondition( COND_HEAVY_DAMAGE ) ) )
+ {
+ // Don't go to combat if you're following the player
+ return NPC_STATE_ALERT;
+ }
+ StopFollowing();
+ }
+ }
+ else if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ {
+ // Stop following if you take damage
+ if ( GetFollowTarget() )
+ StopFollowing();
+ }
+ break;
+
+ case NPC_STATE_COMBAT:
+ {
+ CBaseEntity *pEnemy = GetEnemy();
+ if ( pEnemy != NULL )
+ {
+ if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert
+ {
+ // Strip enemy when going to alert
+ SetEnemy( NULL );
+ return NPC_STATE_ALERT;
+ }
+ // Follow if only scared a little
+ if ( GetFollowTarget() )
+ {
+ return NPC_STATE_ALERT;
+ }
+
+ if ( HasCondition( COND_SEE_ENEMY ) )
+ {
+ m_flFearTime = gpGlobals->curtime;
+ return NPC_STATE_COMBAT;
+ }
+
+ }
+ }
+ break;
+ }
+
+ return BaseClass::SelectIdealState();
+}
+
+int CNPC_Scientist::FriendNumber( int arrayNumber )
+{
+ static int array[3] = { 1, 2, 0 };
+ if ( arrayNumber < 3 )
+ return array[ arrayNumber ];
+ return arrayNumber;
+}
+
+float CNPC_Scientist::TargetDistance( void )
+{
+ CBaseEntity *pFollowTarget = GetFollowTarget();
+
+ // If we lose the player, or he dies, return a really large distance
+ if ( pFollowTarget == NULL || !pFollowTarget->IsAlive() )
+ return 1e6;
+
+ return (pFollowTarget->WorldSpaceCenter() - WorldSpaceCenter()).Length();
+}
+
+bool CNPC_Scientist::IsValidEnemy( CBaseEntity *pEnemy )
+{
+ if( pEnemy->m_iClassname == s_iszBarnacleClassname )
+ {
+ // Scientists ignore barnacles rather than freak out.(sjb)
+ return false;
+ }
+
+ return BaseClass::IsValidEnemy(pEnemy);
+}
+
+
+//=========================================================
+// Dead Scientist PROP
+//=========================================================
+class CNPC_DeadScientist : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_DeadScientist, CAI_BaseNPC );
+public:
+
+ void Spawn( void );
+ Class_T Classify ( void ) { return CLASS_NONE; }
+
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ float MaxYawSpeed ( void ) { return 8.0f; }
+
+ int m_iPose;// which sequence to display -- temporary, don't need to save
+ int m_iDesiredSequence;
+ static char *m_szPoses[7];
+};
+
+
+char *CNPC_DeadScientist::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" };
+
+bool CNPC_DeadScientist::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( FStrEq( szKeyName, "pose" ) )
+ m_iPose = atoi( szValue );
+ else
+ BaseClass::KeyValue( szKeyName, szValue );
+
+ return true;
+}
+
+LINK_ENTITY_TO_CLASS( monster_scientist_dead, CNPC_DeadScientist );
+
+//
+// ********** DeadScientist SPAWN **********
+//
+void CNPC_DeadScientist::Spawn( void )
+{
+ PrecacheModel("models/scientist.mdl");
+ SetModel( "models/scientist.mdl" );
+
+ ClearEffects();
+ SetSequence( 0 );
+ m_bloodColor = BLOOD_COLOR_RED;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ if ( m_nBody == -1 )
+ {// -1 chooses a random head
+ m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1);// pick a head, any head
+ }
+ // Luther is black, make his hands black
+ if ( m_nBody == HEAD_LUTHER )
+ m_nSkin = 1;
+ else
+ m_nSkin = 0;
+
+ SetSequence( LookupSequence( m_szPoses[m_iPose] ) );
+
+ if ( GetSequence() == -1)
+ {
+ Msg ( "Dead scientist with bad pose\n" );
+ }
+
+ m_iHealth = 0.0;//gSkillData.barneyHealth;
+
+ NPCInitDead();
+
+}
+
+
+//=========================================================
+// Sitting Scientist PROP
+//=========================================================
+
+
+LINK_ENTITY_TO_CLASS( monster_sitting_scientist, CNPC_SittingScientist );
+
+//IMPLEMENT_CUSTOM_AI( monster_sitting_scientist, CNPC_SittingScientist );
+
+//IMPLEMENT_SERVERCLASS_ST( CNPC_SittingScientist, DT_NPC_SittingScientist )
+//END_SEND_TABLE()
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_SittingScientist )
+ DEFINE_FIELD( m_iHeadTurn, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flResponseDelay, FIELD_FLOAT ),
+ //DEFINE_FIELD( m_baseSequence, FIELD_INTEGER ),
+
+ DEFINE_THINKFUNC( SittingThink ),
+END_DATADESC()
+
+
+// animation sequence aliases
+typedef enum
+{
+SITTING_ANIM_sitlookleft,
+SITTING_ANIM_sitlookright,
+SITTING_ANIM_sitscared,
+SITTING_ANIM_sitting2,
+SITTING_ANIM_sitting3
+} SITTING_ANIM;
+
+
+//
+// ********** Scientist SPAWN **********
+//
+void CNPC_SittingScientist::Spawn( )
+{
+ PrecacheModel("models/scientist.mdl");
+ SetModel("models/scientist.mdl");
+ Precache();
+
+ InitBoneControllers();
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_iHealth = 50;
+
+ m_bloodColor = BLOOD_COLOR_RED;
+ m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result )
+
+ m_NPCState = NPC_STATE_NONE;
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_TURN_HEAD );
+
+ m_spawnflags |= SF_NPC_PREDISASTER; // predisaster only!
+
+ if ( m_nBody == -1 )
+ {// -1 chooses a random head
+ m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1 );// pick a head, any head
+ }
+ // Luther is black, make his hands black
+ if ( m_nBody == HEAD_LUTHER )
+ m_nBody = 1;
+
+ UTIL_DropToFloor( this,MASK_SOLID );
+
+ NPCInit();
+
+ SetThink (&CNPC_SittingScientist::SittingThink);
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ m_baseSequence = LookupSequence( "sitlookleft" );
+ SetSequence( m_baseSequence + random->RandomInt(0,4) );
+ ResetSequenceInfo( );
+}
+
+void CNPC_SittingScientist::Precache( void )
+{
+ m_baseSequence = LookupSequence( "sitlookleft" );
+ TalkInit();
+}
+
+int CNPC_SittingScientist::FriendNumber( int arrayNumber )
+{
+ static int array[3] = { 2, 1, 0 };
+ if ( arrayNumber < 3 )
+ return array[ arrayNumber ];
+ return arrayNumber;
+}
+
+//=========================================================
+// sit, do stuff
+//=========================================================
+void CNPC_SittingScientist::SittingThink( void )
+{
+ CBaseEntity *pent;
+
+ StudioFrameAdvance( );
+
+ // try to greet player
+ //FIXMEFIXME
+
+ //MB - don't greet, done by base talker
+ if ( 0 && GetExpresser()->CanSpeakConcept( TLK_HELLO ) )
+ {
+ pent = FindNearestFriend(true);
+ if (pent)
+ {
+ float yaw = VecToYaw(pent->GetAbsOrigin() - GetAbsOrigin()) - GetAbsAngles().y;
+
+ if (yaw > 180) yaw -= 360;
+ if (yaw < -180) yaw += 360;
+
+ if (yaw > 0)
+ SetSequence( m_baseSequence + SITTING_ANIM_sitlookleft );
+ else
+ SetSequence ( m_baseSequence + SITTING_ANIM_sitlookright );
+
+ ResetSequenceInfo( );
+ SetCycle( 0 );
+ SetBoneController( 0, 0 );
+
+ GetExpresser()->Speak( TLK_HELLO );
+ }
+ }
+ else if ( IsSequenceFinished() )
+ {
+ int i = random->RandomInt(0,99);
+ m_iHeadTurn = 0;
+
+ if (m_flResponseDelay && gpGlobals->curtime > m_flResponseDelay)
+ {
+ // respond to question
+ GetExpresser()->Speak( TLK_QUESTION );
+ SetSequence( m_baseSequence + SITTING_ANIM_sitscared );
+ m_flResponseDelay = 0;
+ }
+ else if (i < 30)
+ {
+ SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );
+
+ // turn towards player or nearest friend and speak
+
+ //FIXME
+ /*/ if (!FBitSet(m_nSpeak, bit_saidHelloPlayer))
+ pent = FindNearestFriend(TRUE);
+ else*/
+ pent = FindNamedEntity( "!nearestfriend" );
+
+ if (!FIdleSpeak() || !pent)
+ {
+ m_iHeadTurn = random->RandomInt(0,8) * 10 - 40;
+ SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );
+ }
+ else
+ {
+ // only turn head if we spoke
+ float yaw = VecToYaw(pent->GetAbsOrigin() - GetAbsOrigin()) - GetAbsAngles().y;
+
+ if (yaw > 180) yaw -= 360;
+ if (yaw < -180) yaw += 360;
+
+ if (yaw > 0)
+ SetSequence( m_baseSequence + SITTING_ANIM_sitlookleft );
+ else
+ SetSequence( m_baseSequence + SITTING_ANIM_sitlookright );
+
+ //ALERT(at_console, "sitting speak\n");
+ }
+ }
+ else if (i < 60)
+ {
+ SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );
+ m_iHeadTurn = random->RandomInt(0,8) * 10 - 40;
+ if ( random->RandomInt(0,99) < 5)
+ {
+ //ALERT(at_console, "sitting speak2\n");
+ FIdleSpeak();
+ }
+ }
+ else if (i < 80)
+ {
+ SetSequence( m_baseSequence + SITTING_ANIM_sitting2 );
+ }
+ else if (i < 100)
+ {
+ SetSequence( m_baseSequence + SITTING_ANIM_sitscared );
+ }
+
+ ResetSequenceInfo( );
+ SetCycle( 0 );
+ SetBoneController( 0, m_iHeadTurn );
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+// prepare sitting scientist to answer a question
+void CNPC_SittingScientist::SetAnswerQuestion( CNPCSimpleTalker *pSpeaker )
+{
+ m_flResponseDelay = gpGlobals->curtime + random->RandomFloat(3, 4);
+ SetSpeechTarget( (CNPCSimpleTalker *)pSpeaker );
+}
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( monster_scientist, CNPC_Scientist )
+
+ DECLARE_TASK( TASK_SAY_HEAL )
+ DECLARE_TASK( TASK_HEAL )
+ DECLARE_TASK( TASK_SAY_FEAR )
+ DECLARE_TASK( TASK_RUN_PATH_SCARED )
+ DECLARE_TASK( TASK_SCREAM )
+ DECLARE_TASK( TASK_RANDOM_SCREAM )
+ DECLARE_TASK( TASK_MOVE_TO_TARGET_RANGE_SCARED )
+
+ DECLARE_ACTIVITY( ACT_EXCITED )
+
+ //=========================================================
+ // > SCHED_SCI_HEAL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_HEAL,
+
+ " Tasks"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_MOVE_TO_TARGET_RANGE 50"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_FOLLOWTARGET"
+ " TASK_FACE_IDEAL 0"
+ " TASK_SAY_HEAL 0"
+ " TASK_PLAY_SEQUENCE_FACE_TARGET ACTIVITY:ACT_ARM"
+ " TASK_HEAL 0"
+ " TASK_PLAY_SEQUENCE_FACE_TARGET ACTIVITY:ACT_DISARM"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_FOLLOWTARGET
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FOLLOWTARGET,
+
+ " Tasks"
+// " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_STOPFOLLOWING"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_MOVE_TO_TARGET_RANGE 128"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_TARGET_FACE"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_STOPFOLLOWING
+ //=========================================================
+// DEFINE_SCHEDULE
+// (
+// SCHED_SCI_STOPFOLLOWING,
+//
+// " Tasks"
+// " TASK_TALKER_CANT_FOLLOW 0"
+// " "
+// " Interrupts"
+// )
+
+ //=========================================================
+ // > SCHED_SCI_FACETARGET
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FACETARGET,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_TARGET 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_SCI_FOLLOWTARGET"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ " COND_HEAR_COMBAT"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_COVER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_COVER,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_PANIC"
+ " TASK_STOP_MOVING 0"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_RUN_PATH_SCARED 0"
+ " TASK_TURN_LEFT 179"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_SCI_HIDE"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_HIDE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_HIDE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_PANIC"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_WAIT_RANDOM 10"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_ENEMY"
+ " COND_SEE_HATE"
+ " COND_SEE_FEAR"
+ " COND_SEE_DISLIKE"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_IDLESTAND
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_IDLESTAND,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2"
+ " TASK_TALKER_HEADRESET 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_SMELL"
+ " COND_PROVOKED"
+ " COND_HEAR_COMBAT"
+ " COND_GIVE_WAY"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_PANIC
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_PANIC,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_SCREAM 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_EXCITED"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " "
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_FOLLOWSCARED
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FOLLOWSCARED,
+
+ " Tasks"
+ " TASK_GET_PATH_TO_TARGET 0"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_FOLLOWTARGET"
+ " TASK_MOVE_TO_TARGET_RANGE_SCARED 128"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_FACETARGETSCARED
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FACETARGETSCARED,
+
+ " Tasks"
+ " TASK_FACE_TARGET 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_SCI_FOLLOWSCARED"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_FEAR
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_FEAR,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_ENEMY 0"
+ " TASK_SAY_FEAR 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_SCI_STARTLE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_SCI_STARTLE,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCI_PANIC"
+ " TASK_RANDOM_SCREAM 0.3"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_CROUCH"
+ " TASK_RANDOM_SCREAM 0.1"
+ " TASK_PLAY_SEQUENCE_FACE_ENEMY ACTIVITY:ACT_CROUCHIDLE"
+ " TASK_WAIT_RANDOM 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_SEE_ENEMY"
+ " COND_SEE_HATE"
+ " COND_SEE_FEAR"
+ " COND_SEE_DISLIKE"
+ )
+
+AI_END_CUSTOM_NPC()
diff --git a/game/server/hl1/hl1_npc_scientist.h b/game/server/hl1/hl1_npc_scientist.h
new file mode 100644
index 0000000..858883c
--- /dev/null
+++ b/game/server/hl1/hl1_npc_scientist.h
@@ -0,0 +1,139 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_SCIENTIST_H
+#define NPC_SCIENTIST_H
+
+#include "hl1_npc_talker.h"
+
+//=========================================================
+//=========================================================
+class CNPC_Scientist : public CHL1NPCTalker
+{
+ DECLARE_CLASS( CNPC_Scientist, CHL1NPCTalker );
+public:
+
+// DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+
+ void Precache( void );
+ void Spawn( void );
+ void Activate();
+ Class_T Classify( void );
+ int GetSoundInterests ( void );
+
+ virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set );
+
+ virtual int ObjectCaps( void ) { return UsableNPCObjectCaps(BaseClass::ObjectCaps()); }
+ float MaxYawSpeed( void );
+
+ float TargetDistance( void );
+ bool IsValidEnemy( CBaseEntity *pEnemy );
+
+
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ void Heal( void );
+ bool CanHeal( void );
+
+ int TranslateSchedule( int scheduleType );
+ void HandleAnimEvent( animevent_t *pEvent );
+ int SelectSchedule( void );
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+
+ NPC_STATE SelectIdealState ( void );
+
+ int FriendNumber( int arrayNumber );
+
+ bool DisregardEnemy( CBaseEntity *pEnemy ) { return !pEnemy->IsAlive() || (gpGlobals->curtime - m_flFearTime) > 15; }
+
+ void TalkInit( void );
+
+ void DeclineFollowing( void );
+
+ bool CanBecomeRagdoll( void );
+ bool ShouldGib( const CTakeDamageInfo &info );
+
+ void SUB_StartLVFadeOut( float delay = 10.0f, bool bNotSolid = true );
+ void SUB_LVFadeOut( void );
+
+ void Scream( void );
+
+ Activity GetStoppedActivity( void );
+ Activity NPC_TranslateActivity( Activity newActivity );
+
+ void PainSound( const CTakeDamageInfo &info );
+ void DeathSound( const CTakeDamageInfo &info );
+
+ enum
+ {
+ SCHED_SCI_HEAL = BaseClass::NEXT_SCHEDULE,
+ SCHED_SCI_FOLLOWTARGET,
+ SCHED_SCI_STOPFOLLOWING,
+ SCHED_SCI_FACETARGET,
+ SCHED_SCI_COVER,
+ SCHED_SCI_HIDE,
+ SCHED_SCI_IDLESTAND,
+ SCHED_SCI_PANIC,
+ SCHED_SCI_FOLLOWSCARED,
+ SCHED_SCI_FACETARGETSCARED,
+ SCHED_SCI_FEAR,
+ SCHED_SCI_STARTLE,
+ };
+
+ enum
+ {
+ TASK_SAY_HEAL = BaseClass::NEXT_TASK,
+ TASK_HEAL,
+ TASK_SAY_FEAR,
+ TASK_RUN_PATH_SCARED,
+ TASK_SCREAM,
+ TASK_RANDOM_SCREAM,
+ TASK_MOVE_TO_TARGET_RANGE_SCARED,
+ };
+
+ DEFINE_CUSTOM_AI;
+
+private:
+
+ float m_flFearTime;
+ float m_flHealTime;
+ float m_flPainTime;
+ //float m_flResponseDelay;
+};
+
+//=========================================================
+// Sitting Scientist PROP
+//=========================================================
+
+class CNPC_SittingScientist : public CNPC_Scientist // kdb: changed from public CBaseMonster so he can speak
+{
+ DECLARE_CLASS( CNPC_SittingScientist, CNPC_Scientist );
+public:
+
+// DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ int FriendNumber( int arrayNumber );
+
+ void SittingThink( void );
+
+ virtual void SetAnswerQuestion( CNPCSimpleTalker *pSpeaker );
+ int m_baseSequence;
+ int m_iHeadTurn;
+ float m_flResponseDelay;
+
+ //DEFINE_CUSTOM_AI;
+};
+
+#endif // NPC_SCIENTIST_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_snark.cpp b/game/server/hl1/hl1_npc_snark.cpp
new file mode 100644
index 0000000..cb33749
--- /dev/null
+++ b/game/server/hl1/hl1_npc_snark.cpp
@@ -0,0 +1,527 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Projectile shot from the MP5
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "soundent.h"
+#include "engine/IEngineSound.h"
+#include "ai_senses.h"
+#include "hl1_npc_snark.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+
+
+ConVar sk_snark_health ( "sk_snark_health", "0" );
+ConVar sk_snark_dmg_bite ( "sk_snark_dmg_bite", "0" );
+ConVar sk_snark_dmg_pop ( "sk_snark_dmg_pop", "0" );
+
+
+LINK_ENTITY_TO_CLASS( monster_snark, CSnark);
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CSnark )
+ DEFINE_FIELD( m_flDie, FIELD_TIME ),
+ DEFINE_FIELD( m_vecTarget, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flNextHunt, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextHit, FIELD_TIME ),
+ DEFINE_FIELD( m_posPrev, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_iMyClass, FIELD_INTEGER ),
+
+ DEFINE_ENTITYFUNC( SuperBounceTouch ),
+ DEFINE_THINKFUNC( HuntThink ),
+END_DATADESC()
+
+
+#define SQUEEK_DETONATE_DELAY 15.0
+#define SNARK_EXPLOSION_VOLUME 512
+
+
+enum w_squeak_e {
+ WSQUEAK_IDLE1 = 0,
+ WSQUEAK_FIDGET,
+ WSQUEAK_JUMP,
+ WSQUEAK_RUN,
+};
+
+float CSnark::m_flNextBounceSoundTime = 0;
+
+void CSnark::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheModel( "models/w_squeak2.mdl" );
+
+ PrecacheScriptSound( "Snark.Die" );
+ PrecacheScriptSound( "Snark.Gibbed" );
+ PrecacheScriptSound( "Snark.Squeak" );
+ PrecacheScriptSound( "Snark.Deploy" );
+ PrecacheScriptSound( "Snark.Bounce" );
+
+}
+
+
+void CSnark::Spawn( void )
+{
+ Precache();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
+ SetFriction(1.0);
+
+ SetModel( "models/w_squeak2.mdl" );
+ UTIL_SetSize( this, Vector( -4, -4, 0 ), Vector( 4, 4, 8 ) );
+
+ SetBloodColor( BLOOD_COLOR_YELLOW );
+
+ SetTouch( &CSnark::SuperBounceTouch );
+ SetThink( &CSnark::HuntThink );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ m_flNextHit = gpGlobals->curtime;
+ m_flNextHunt = gpGlobals->curtime + 1E6;
+ m_flNextBounceSoundTime = gpGlobals->curtime;
+
+ AddFlag( FL_AIMTARGET | FL_NPC );
+ m_takedamage = DAMAGE_YES;
+
+ m_iHealth = sk_snark_health.GetFloat();
+ m_iMaxHealth = m_iHealth;
+
+ SetGravity( UTIL_ScaleForGravity( 400 ) ); // use a lower gravity for snarks
+ SetFriction( 0.5 );
+
+ SetDamage( sk_snark_dmg_pop.GetFloat() );
+
+ m_flDie = gpGlobals->curtime + SQUEEK_DETONATE_DELAY;
+
+ m_flFieldOfView = 0; // 180 degrees
+
+ if ( GetOwnerEntity() )
+ m_hOwner = GetOwnerEntity();
+
+ m_flNextBounceSoundTime = gpGlobals->curtime;// reset each time a snark is spawned.
+
+ SetSequence( WSQUEAK_RUN );
+ ResetSequenceInfo( );
+
+ m_iMyClass = CLASS_NONE;
+
+ m_posPrev = Vector( 0, 0, 0 );
+}
+
+
+Class_T CSnark::Classify( void )
+{
+ if ( m_iMyClass != CLASS_NONE )
+ return m_iMyClass; // protect against recursion
+
+ if ( GetEnemy() != NULL )
+ {
+ m_iMyClass = CLASS_INSECT; // no one cares about it
+ switch( GetEnemy()->Classify( ) )
+ {
+ case CLASS_PLAYER:
+ case CLASS_HUMAN_PASSIVE:
+ case CLASS_HUMAN_MILITARY:
+ m_iMyClass = CLASS_NONE;
+ return CLASS_ALIEN_MILITARY; // barney's get mad, grunts get mad at it
+ }
+ m_iMyClass = CLASS_NONE;
+ }
+
+ return CLASS_ALIEN_BIOWEAPON;
+}
+
+
+void CSnark::Event_Killed( const CTakeDamageInfo &inputInfo )
+{
+// pev->model = iStringNull;// make invisible
+ SetThink( &CSnark::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ SetTouch( NULL );
+
+ // since squeak grenades never leave a body behind, clear out their takedamage now.
+ // Squeaks do a bit of radius damage when they pop, and that radius damage will
+ // continue to call this function unless we acknowledge the Squeak's death now. (sjb)
+ m_takedamage = DAMAGE_NO;
+
+ // play squeek blast
+ CPASAttenuationFilter filter( this, 0.5 );
+ EmitSound( filter, entindex(), "Snark.Die" );
+
+ CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), SNARK_EXPLOSION_VOLUME, 3.0 );
+
+ UTIL_BloodDrips( WorldSpaceCenter(), Vector( 0, 0, 0 ), BLOOD_COLOR_YELLOW, 80 );
+
+ if ( m_hOwner != NULL )
+ {
+ RadiusDamage( CTakeDamageInfo( this, m_hOwner, GetDamage(), DMG_BLAST ), GetAbsOrigin(), GetDamage() * 2.5, CLASS_NONE, NULL );
+ }
+ else
+ {
+ RadiusDamage( CTakeDamageInfo( this, this, GetDamage(), DMG_BLAST ), GetAbsOrigin(), GetDamage() * 2.5, CLASS_NONE, NULL );
+ }
+
+ // reset owner so death message happens
+ if ( m_hOwner != NULL )
+ SetOwnerEntity( m_hOwner );
+
+ CTakeDamageInfo info = inputInfo;
+ int iGibDamage = g_pGameRules->Damage_GetShouldGibCorpse();
+ info.SetDamageType( iGibDamage );
+
+ BaseClass::Event_Killed( info );
+}
+
+
+bool CSnark::Event_Gibbed( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Snark.Gibbed" );
+
+ return BaseClass::Event_Gibbed( info );
+}
+
+
+void CSnark::HuntThink( void )
+{
+ if (!IsInWorld())
+ {
+ SetTouch( NULL );
+ UTIL_Remove( this );
+ return;
+ }
+
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ //FIXME: There's a problem in this movetype that causes it to set a ground entity but never recheck to clear it
+ // For now, we stomp it clear and force it to revalidate -- jdw
+
+ SetGroundEntity( NULL );
+ PhysicsStepRecheckGround();
+
+ // explode when ready
+ if ( gpGlobals->curtime >= m_flDie )
+ {
+ g_vecAttackDir = GetAbsVelocity();
+ VectorNormalize( g_vecAttackDir );
+ m_iHealth = -1;
+ CTakeDamageInfo info( this, this, 1, DMG_GENERIC );
+ Event_Killed( info );
+ return;
+ }
+
+ // float
+ if ( GetWaterLevel() != 0)
+ {
+ if ( GetMoveType() == MOVETYPE_FLYGRAVITY )
+ {
+ SetMoveType( MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM );
+ }
+
+ Vector vecVel = GetAbsVelocity();
+ vecVel *= 0.9;
+ vecVel.z += 8.0;
+ SetAbsVelocity( vecVel );
+ }
+ else if ( GetMoveType() == MOVETYPE_FLY )
+ {
+ SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_CUSTOM );
+ }
+
+ // return if not time to hunt
+ if ( m_flNextHunt > gpGlobals->curtime )
+ return;
+
+ m_flNextHunt = gpGlobals->curtime + 2.0;
+
+ Vector vecFlat = GetAbsVelocity();
+ vecFlat.z = 0;
+ VectorNormalize( vecFlat );
+
+ if ( GetEnemy() == NULL || !GetEnemy()->IsAlive() )
+ {
+ // find target, bounce a bit towards it.
+ GetSenses()->Look( 1024 );
+ SetEnemy( BestEnemy() );
+ }
+
+ // squeek if it's about time blow up
+ if ( (m_flDie - gpGlobals->curtime <= 0.5) && (m_flDie - gpGlobals->curtime >= 0.3) )
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Snark.Squeak" );
+ CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 256, 0.25 );
+ }
+
+ // higher pitch as squeeker gets closer to detonation time
+ float flpitch = 155.0 - 60.0 * ( (m_flDie - gpGlobals->curtime) / SQUEEK_DETONATE_DELAY );
+ if ( flpitch < 80 )
+ flpitch = 80;
+
+ if ( GetEnemy() != NULL )
+ {
+ if ( FVisible( GetEnemy() ) )
+ {
+ m_vecTarget = GetEnemy()->EyePosition() - GetAbsOrigin();
+ VectorNormalize( m_vecTarget );
+ }
+
+ float flVel = GetAbsVelocity().Length();
+ float flAdj = 50.0 / ( flVel + 10.0 );
+
+ if ( flAdj > 1.2 )
+ flAdj = 1.2;
+
+ // ALERT( at_console, "think : enemy\n");
+
+ // ALERT( at_console, "%.0f %.2f %.2f %.2f\n", flVel, m_vecTarget.x, m_vecTarget.y, m_vecTarget.z );
+
+ SetAbsVelocity( GetAbsVelocity() * flAdj + (m_vecTarget * 300) );
+ }
+
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ SetLocalAngularVelocity( QAngle( 0, 0, 0 ) );
+ }
+ else
+ {
+ QAngle angVel = GetLocalAngularVelocity();
+ if ( angVel == QAngle( 0, 0, 0 ) )
+ {
+ angVel.x = random->RandomFloat( -100, 100 );
+ angVel.z = random->RandomFloat( -100, 100 );
+ SetLocalAngularVelocity( angVel );
+ }
+ }
+
+ if ( ( GetAbsOrigin() - m_posPrev ).Length() < 1.0 )
+ {
+ Vector vecVel = GetAbsVelocity();
+ vecVel.x = random->RandomFloat( -100, 100 );
+ vecVel.y = random->RandomFloat( -100, 100 );
+ SetAbsVelocity( vecVel );
+ }
+
+ m_posPrev = GetAbsOrigin();
+
+ QAngle angles;
+ VectorAngles( GetAbsVelocity(), angles );
+ angles.z = 0;
+ angles.x = 0;
+ SetAbsAngles( angles );
+}
+
+unsigned int CSnark::PhysicsSolidMaskForEntity( void ) const
+{
+ unsigned int iMask = BaseClass::PhysicsSolidMaskForEntity();
+
+ iMask &= ~CONTENTS_MONSTERCLIP;
+
+ return iMask;
+}
+
+
+// Custom collision that provides a good bounce when we hit walls
+// and also gives gravity velocity so the snarks fall off of edges.
+void CSnark::ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity )
+{
+ // Get the impact surface's friction.
+ float flSurfaceFriction;
+ physprops->GetPhysicsProperties( trace.surface.surfaceProps, NULL, NULL, &flSurfaceFriction, NULL );
+
+ Vector vecAbsVelocity = GetAbsVelocity();
+
+ // If we hit a wall
+ if ( trace.plane.normal.z <= 0.7 ) // Floor
+ {
+ Vector vecDir = vecAbsVelocity;
+
+ float speed = vecDir.Length();
+
+ VectorNormalize( vecDir );
+
+ float hitDot = DotProduct( trace.plane.normal, -vecDir );
+
+ Vector vReflection = 2.0f * trace.plane.normal * hitDot + vecDir;
+
+ SetAbsVelocity( vReflection * speed * 0.6f );
+
+ return;
+ }
+
+ // Stop if on ground.
+ // Get the total velocity (player + conveyors, etc.)
+ VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity );
+ float flSpeedSqr = DotProduct( vecVelocity, vecVelocity );
+
+ // Verify that we have an entity.
+ CBaseEntity *pEntity = trace.m_pEnt;
+ Assert( pEntity );
+
+ if ( vecVelocity.z < ( 800 * gpGlobals->frametime ) )
+ {
+ vecAbsVelocity.z = 0.0f;
+
+ // Recompute speedsqr based on the new absvel
+ VectorAdd( vecAbsVelocity, GetBaseVelocity(), vecVelocity );
+ flSpeedSqr = DotProduct( vecVelocity, vecVelocity );
+ }
+ SetAbsVelocity( vecAbsVelocity );
+
+ if ( flSpeedSqr < ( 30 * 30 ) )
+ {
+ if ( pEntity->IsStandable() )
+ {
+ SetGroundEntity( pEntity );
+ }
+
+ // Reset velocities.
+ SetAbsVelocity( vec3_origin );
+ SetLocalAngularVelocity( vec3_angle );
+ }
+ else
+ {
+ vecAbsVelocity += GetBaseVelocity();
+ vecAbsVelocity *= ( 1.0f - trace.fraction ) * gpGlobals->frametime * flSurfaceFriction;
+ PhysicsPushEntity( vecAbsVelocity, &trace );
+ }
+}
+
+void CSnark::SuperBounceTouch( CBaseEntity *pOther )
+{
+ float flpitch;
+ trace_t tr;
+ tr = CBaseEntity::GetTouchTrace( );
+
+ // don't hit the guy that launched this grenade
+ if ( GetOwnerEntity() && ( pOther == GetOwnerEntity() ) )
+ return;
+
+ // at least until we've bounced once
+ SetOwnerEntity( NULL );
+
+ QAngle angles = GetAbsAngles();
+ angles.x = 0;
+ angles.z = 0;
+ SetAbsAngles( angles );
+
+ // avoid bouncing too much
+ if ( m_flNextHit > gpGlobals->curtime)
+ return;
+
+ // higher pitch as squeeker gets closer to detonation time
+ flpitch = 155.0 - 60.0 * ( ( m_flDie - gpGlobals->curtime ) / SQUEEK_DETONATE_DELAY );
+
+ if ( pOther->m_takedamage && m_flNextAttack < gpGlobals->curtime )
+ {
+ // attack!
+
+ // make sure it's me who has touched them
+ if ( tr.m_pEnt == pOther )
+ {
+ // and it's not another squeakgrenade
+ if ( tr.m_pEnt->GetModelIndex() != GetModelIndex() )
+ {
+ // ALERT( at_console, "hit enemy\n");
+ ClearMultiDamage();
+
+ Vector vecForward;
+ AngleVectors( GetAbsAngles(), &vecForward );
+
+ if ( m_hOwner != NULL )
+ {
+ CTakeDamageInfo info( this, m_hOwner, sk_snark_dmg_bite.GetFloat(), DMG_SLASH );
+ CalculateMeleeDamageForce( &info, vecForward, tr.endpos );
+ pOther->DispatchTraceAttack( info, vecForward, &tr );
+ }
+ else
+ {
+ CTakeDamageInfo info( this, this, sk_snark_dmg_bite.GetFloat(), DMG_SLASH );
+ CalculateMeleeDamageForce( &info, vecForward, tr.endpos );
+ pOther->DispatchTraceAttack( info, vecForward, &tr );
+ }
+
+ ApplyMultiDamage();
+
+ SetDamage( GetDamage() + sk_snark_dmg_pop.GetFloat() ); // add more explosion damage
+ // m_flDie += 2.0; // add more life
+
+ // make bite sound
+ CPASAttenuationFilter filter( this );
+ CSoundParameters params;
+ if ( GetParametersForSound( "Snark.Deploy", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ ep.m_nPitch = (int)flpitch;
+
+ EmitSound( filter, entindex(), ep );
+ }
+ m_flNextAttack = gpGlobals->curtime + 0.5;
+ }
+ }
+ else
+ {
+ // ALERT( at_console, "been hit\n");
+ }
+ }
+
+ m_flNextHit = gpGlobals->curtime + 0.1;
+ m_flNextHunt = gpGlobals->curtime;
+
+ if ( g_pGameRules->IsMultiplayer() )
+ {
+ // in multiplayer, we limit how often snarks can make their bounce sounds to prevent overflows.
+ if ( gpGlobals->curtime < m_flNextBounceSoundTime )
+ {
+ // too soon!
+ return;
+ }
+ }
+
+ if ( !( GetFlags() & FL_ONGROUND ) )
+ {
+ // play bounce sound
+ CPASAttenuationFilter filter2( this );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "Snark.Bounce", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ ep.m_nPitch = (int)flpitch;
+
+ EmitSound( filter2, entindex(), ep );
+ }
+
+ CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 256, 0.25 );
+ }
+ else
+ {
+ // skittering sound
+ CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 100, 0.1 );
+ }
+
+ m_flNextBounceSoundTime = gpGlobals->curtime + 0.5;// half second.
+}
+
+
+bool CSnark::IsValidEnemy( CBaseEntity *pEnemy )
+{
+ return CHL1BaseNPC::IsValidEnemy( pEnemy );
+}
+
diff --git a/game/server/hl1/hl1_npc_snark.h b/game/server/hl1/hl1_npc_snark.h
new file mode 100644
index 0000000..d28b489
--- /dev/null
+++ b/game/server/hl1/hl1_npc_snark.h
@@ -0,0 +1,57 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Projectile shot from the MP5
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+
+#ifndef NPC_SNARK_H
+#define NPC_SNARK_H
+
+
+#include "hl1_ai_basenpc.h"
+
+
+class CSnark : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CSnark, CHL1BaseNPC );
+public:
+
+ DECLARE_DATADESC();
+
+ void Precache( void );
+ void Spawn( void );
+ Class_T Classify( void );
+ void Event_Killed( const CTakeDamageInfo &info );
+ bool Event_Gibbed( const CTakeDamageInfo &info );
+ void HuntThink( void );
+ void SuperBounceTouch( CBaseEntity *pOther );
+
+ virtual void ResolveFlyCollisionCustom( trace_t &trace, Vector &vecVelocity );
+
+ virtual unsigned int PhysicsSolidMaskForEntity( void ) const;
+
+ virtual bool ShouldGib( const CTakeDamageInfo &info ) { return false; }
+ static float m_flNextBounceSoundTime;
+
+ virtual bool IsValidEnemy( CBaseEntity *pEnemy );
+
+private:
+ Class_T m_iMyClass;
+ float m_flDie;
+ Vector m_vecTarget;
+ float m_flNextHunt;
+ float m_flNextHit;
+ Vector m_posPrev;
+ EHANDLE m_hOwner;
+};
+
+
+#endif // NPC_SNARK_H
diff --git a/game/server/hl1/hl1_npc_talker.cpp b/game/server/hl1/hl1_npc_talker.cpp
new file mode 100644
index 0000000..2797cec
--- /dev/null
+++ b/game/server/hl1/hl1_npc_talker.cpp
@@ -0,0 +1,728 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include "cbase.h"
+#include "hl1_npc_talker.h"
+#include "scripted.h"
+#include "soundent.h"
+#include "animation.h"
+#include "entitylist.h"
+#include "ai_navigator.h"
+#include "ai_motor.h"
+#include "player.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "npcevent.h"
+#include "ai_interactions.h"
+#include "doors.h"
+
+#include "effect_dispatch_data.h"
+#include "te_effect_dispatch.h"
+#include "hl1_ai_basenpc.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+
+ConVar hl1_debug_sentence_volume( "hl1_debug_sentence_volume", "0" );
+ConVar hl1_fixup_sentence_sndlevel( "hl1_fixup_sentence_sndlevel", "1" );
+
+//#define TALKER_LOOK 0
+
+BEGIN_DATADESC( CHL1NPCTalker )
+
+ DEFINE_ENTITYFUNC( Touch ),
+ DEFINE_FIELD( m_bInBarnacleMouth, FIELD_BOOLEAN ),
+ DEFINE_USEFUNC( FollowerUse ),
+
+END_DATADESC()
+
+void CHL1NPCTalker::RunTask( const Task_t *pTask )
+{
+ switch ( pTask->iTask )
+ {
+ case TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS:
+ {
+ float distance;
+
+ distance = (m_vecLastPosition - GetLocalOrigin()).Length2D();
+
+ // Walk path until far enough away
+ if ( distance > pTask->flTaskData ||
+ GetNavigator()->GetGoalType() == GOALTYPE_NONE )
+ {
+ TaskComplete();
+ GetNavigator()->ClearGoal(); // Stop moving
+ }
+ break;
+ }
+
+
+
+ case TASK_TALKER_CLIENT_STARE:
+ case TASK_TALKER_LOOK_AT_CLIENT:
+ {
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ // track head to the client for a while.
+ if ( m_NPCState == NPC_STATE_IDLE &&
+ !IsMoving() &&
+ !GetExpresser()->IsSpeaking() )
+ {
+
+ if ( pPlayer )
+ {
+ IdleHeadTurn( pPlayer );
+ }
+ }
+ else
+ {
+ // started moving or talking
+ TaskFail( "moved away" );
+ return;
+ }
+
+ if ( pTask->iTask == TASK_TALKER_CLIENT_STARE )
+ {
+ // fail out if the player looks away or moves away.
+ if ( ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).Length2D() > TALKER_STARE_DIST )
+ {
+ // player moved away.
+ TaskFail( NO_TASK_FAILURE );
+ }
+
+ Vector vForward;
+ AngleVectors( GetAbsAngles(), &vForward );
+ if ( UTIL_DotPoints( pPlayer->GetAbsOrigin(), GetAbsOrigin(), vForward ) < m_flFieldOfView )
+ {
+ // player looked away
+ TaskFail( "looked away" );
+ }
+ }
+
+ if ( gpGlobals->curtime > m_flWaitFinished )
+ {
+ TaskComplete( NO_TASK_FAILURE );
+ }
+
+ break;
+ }
+
+ case TASK_WAIT_FOR_MOVEMENT:
+ {
+ if ( GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL)
+ {
+ // ALERT(at_console, "walking, talking\n");
+ IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime );
+ }
+ else if ( GetEnemy() )
+ {
+ IdleHeadTurn( GetEnemy() );
+ }
+
+ BaseClass::RunTask( pTask );
+
+ break;
+ }
+
+ case TASK_FACE_PLAYER:
+ {
+ CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
+
+ if ( pPlayer )
+ {
+ //GetMotor()->SetIdealYaw( pPlayer->GetAbsOrigin() );
+ IdleHeadTurn( pPlayer );
+ if ( gpGlobals->curtime > m_flWaitFinished && GetMotor()->DeltaIdealYaw() < 10 )
+ {
+ TaskComplete();
+ }
+ }
+ else
+ {
+ TaskFail( FAIL_NO_PLAYER );
+ }
+
+ break;
+ }
+
+ case TASK_TALKER_EYECONTACT:
+ {
+ if (!IsMoving() && GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL)
+ {
+ // ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time );
+ IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime );
+ }
+
+ BaseClass::RunTask( pTask );
+
+ break;
+
+ }
+
+
+ default:
+ {
+ if ( GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL)
+ {
+ IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime );
+ }
+ else if ( GetEnemy() && m_NPCState == NPC_STATE_COMBAT )
+ {
+ IdleHeadTurn( GetEnemy() );
+ }
+ else if ( GetFollowTarget() )
+ {
+ IdleHeadTurn( GetFollowTarget() );
+ }
+
+ BaseClass::RunTask( pTask );
+ break;
+ }
+ }
+}
+
+bool CHL1NPCTalker::ShouldGib( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamageType() & DMG_NEVERGIB )
+ return false;
+
+ if ( ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) && m_iHealth < GIB_HEALTH_VALUE ) || ( info.GetDamageType() & DMG_ALWAYSGIB ) )
+ return true;
+
+ return false;
+
+}
+
+void CHL1NPCTalker::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS:
+ {
+ GetNavigator()->SetMovementActivity( ACT_WALK );
+ break;
+ }
+ case TASK_TALKER_SPEAK:
+ // ask question or make statement
+ FIdleSpeak();
+ TaskComplete();
+ break;
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//=========================================================
+// FIdleSpeak
+// ask question of nearby friend, or make statement
+//=========================================================
+int CHL1NPCTalker::FIdleSpeak ( void )
+{
+ if (!IsOkToSpeak())
+ return FALSE;
+
+ // if there is a friend nearby to speak to, play sentence, set friend's response time, return
+ // try to talk to any standing or sitting scientists nearby
+ CBaseEntity *pentFriend = FindNearestFriend( false );
+ CHL1NPCTalker *pentTalker = dynamic_cast<CHL1NPCTalker *>( pentFriend );
+ if (pentTalker && random->RandomInt(0,1) )
+ {
+ Speak( TLK_QUESTION );
+ SetSpeechTarget( pentFriend );
+
+ pentTalker->SetSpeechTarget( this );
+ pentTalker->SetCondition( COND_TALKER_RESPOND_TO_QUESTION );
+ pentTalker->SetSchedule( SCHED_TALKER_IDLE_RESPONSE );
+ pentTalker->GetExpresser()->BlockSpeechUntil( GetExpresser()->GetTimeSpeechComplete() );
+
+ GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + random->RandomFloat(4.8, 5.2) );
+
+ //DevMsg( "Asking some question!\n" );
+ return TRUE;
+ }
+ else if ( random->RandomInt(0,1)) // otherwise, play an idle statement
+ {
+ //DevMsg( "Making idle statement!\n" );
+
+ Speak( TLK_IDLE );
+ // set global min delay for next conversation
+ GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + random->RandomFloat(4.8, 5.2) );
+ return TRUE;
+ }
+
+ // never spoke
+ GetExpresser()->BlockSpeechUntil( 0 );
+ m_flNextIdleSpeechTime = gpGlobals->curtime + 3;
+ return FALSE;
+}
+
+
+
+bool CHL1NPCTalker::IsValidSpeechTarget( int flags, CBaseEntity *pEntity )
+{
+ if ( pEntity == this )
+ return false;
+
+ CHL1NPCTalker *pentTarget = dynamic_cast<CHL1NPCTalker *>( pEntity );
+ if ( pentTarget )
+ {
+ if ( !(flags & AIST_IGNORE_RELATIONSHIP) )
+ {
+ if ( pEntity->IsPlayer() )
+ {
+ if ( !IsPlayerAlly( (CBasePlayer *)pEntity ) )
+ return false;
+ }
+ else
+ {
+ if ( IRelationType( pEntity ) != D_LI )
+ return false;
+ }
+ }
+
+ if ( !pEntity->IsAlive() )
+ // don't dead people
+ return false;
+
+ // Ignore no-target entities
+ if ( pEntity->GetFlags() & FL_NOTARGET )
+ return false;
+
+ CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
+ if ( pNPC )
+ {
+ // If not a NPC for some reason, or in a script.
+ //if ( (pNPC->m_NPCState == NPC_STATE_SCRIPT || pNPC->m_NPCState == NPC_STATE_PRONE))
+ // return false;
+
+ if ( pNPC->IsInAScript() )
+ return false;
+
+ // Don't bother people who don't want to be bothered
+ if ( !pNPC->CanBeUsedAsAFriend() )
+ return false;
+ }
+
+ if ( flags & AIST_FACING_TARGET )
+ {
+ if ( pEntity->IsPlayer() )
+ return HasCondition( COND_SEE_PLAYER );
+ else if ( !FInViewCone( pEntity ) )
+ return false;
+ }
+
+ return FVisible( pEntity );
+ }
+ else
+ return BaseClass::IsValidSpeechTarget( flags, pEntity );
+}
+
+
+int CHL1NPCTalker::SelectSchedule ( void )
+{
+ switch( m_NPCState )
+ {
+ case NPC_STATE_PRONE:
+ {
+ if (m_bInBarnacleMouth)
+ {
+ return SCHED_HL1TALKER_BARNACLE_CHOMP;
+ }
+ else
+ {
+ return SCHED_HL1TALKER_BARNACLE_HIT;
+ }
+ }
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+void CHL1NPCTalker::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "Barney.Close" );
+}
+
+bool CHL1NPCTalker::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
+{
+ if (interactionType == g_interactionBarnacleVictimDangle)
+ {
+ // Force choosing of a new schedule
+ ClearSchedule( "NPC talker being eaten by a barnacle" );
+ m_bInBarnacleMouth = true;
+ return true;
+ }
+ else if ( interactionType == g_interactionBarnacleVictimReleased )
+ {
+ SetState ( NPC_STATE_IDLE );
+
+ CPASAttenuationFilter filter( this );
+
+ CSoundParameters params;
+
+ if ( GetParametersForSound( "Barney.Close", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ ep.m_nPitch = GetExpresser()->GetVoicePitch();
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+ m_bInBarnacleMouth = false;
+ SetAbsVelocity( vec3_origin );
+ SetMoveType( MOVETYPE_STEP );
+ return true;
+ }
+ else if ( interactionType == g_interactionBarnacleVictimGrab )
+ {
+ if ( GetFlags() & FL_ONGROUND )
+ {
+ SetGroundEntity( NULL );
+ }
+
+ if ( GetState() == NPC_STATE_SCRIPT )
+ {
+ if ( m_hCine )
+ {
+ m_hCine->CancelScript();
+ }
+ }
+
+ SetState( NPC_STATE_PRONE );
+ ClearSchedule( "NPC talker grabbed by a barnacle" );
+
+ CTakeDamageInfo info;
+ PainSound( info );
+ return true;
+ }
+ return false;
+}
+
+void CHL1NPCTalker::StartFollowing( CBaseEntity *pLeader )
+{
+ if ( !HasSpawnFlags( SF_NPC_GAG ) )
+ {
+ if ( m_iszUse != NULL_STRING )
+ {
+ PlaySentence( STRING( m_iszUse ), 0.0f );
+ }
+ else
+ {
+ Speak( TLK_STARTFOLLOW );
+ }
+
+ SetSpeechTarget( pLeader );
+ }
+
+ BaseClass::StartFollowing( pLeader );
+}
+
+int CHL1NPCTalker::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
+{
+ if( hl1_debug_sentence_volume.GetBool() )
+ {
+ Msg( "SENTENCE: %s Vol:%f SndLevel:%d\n", GetDebugName(), volume, soundlevel );
+ }
+
+ if( hl1_fixup_sentence_sndlevel.GetBool() )
+ {
+ if( soundlevel < SNDLVL_TALKING )
+ {
+ soundlevel = SNDLVL_TALKING;
+ }
+ }
+
+ return BaseClass::PlayScriptedSentence( pszSentence, delay, volume, soundlevel, bConcurrent, pListener );
+}
+
+Disposition_t CHL1NPCTalker::IRelationType( CBaseEntity *pTarget )
+{
+ if ( pTarget->IsPlayer() )
+ {
+ if ( HasMemory( bits_MEMORY_PROVOKED ) )
+ {
+ return D_HT;
+ }
+ }
+
+ return BaseClass::IRelationType( pTarget );
+}
+
+void CHL1NPCTalker::Touch( CBaseEntity *pOther )
+{
+ if ( m_NPCState == NPC_STATE_SCRIPT )
+ return;
+
+ BaseClass::Touch(pOther);
+}
+
+void CHL1NPCTalker::StopFollowing( void )
+{
+ if ( !(m_afMemory & bits_MEMORY_PROVOKED) )
+ {
+ if ( !HasSpawnFlags( SF_NPC_GAG ) )
+ {
+ if ( m_iszUnUse != NULL_STRING )
+ {
+ PlaySentence( STRING( m_iszUnUse ), 0.0f );
+ }
+ else
+ {
+ Speak( TLK_STOPFOLLOW );
+ }
+
+ SetSpeechTarget( GetFollowTarget() );
+ }
+ }
+
+ BaseClass::StopFollowing();
+}
+
+void CHL1NPCTalker::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ if ( info.GetDamage() >= 1.0 && !(info.GetDamageType() & DMG_SHOCK ) )
+ {
+ UTIL_BloodImpact( ptr->endpos, vecDir, BloodColor(), 4 );
+ }
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+void CHL1NPCTalker::FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ // Don't allow use during a scripted_sentence
+ if ( GetUseTime() > gpGlobals->curtime )
+ return;
+
+ if ( m_hCine && !m_hCine->CanInterrupt() )
+ return;
+
+ if ( pCaller != NULL && pCaller->IsPlayer() )
+ {
+ // Pre-disaster followers can't be used
+ if ( m_spawnflags & SF_NPC_PREDISASTER )
+ {
+ SetSpeechTarget( pCaller );
+ DeclineFollowing();
+ return;
+ }
+ }
+
+ BaseClass::FollowerUse( pActivator, pCaller, useType, value );
+}
+
+int CHL1NPCTalker::TranslateSchedule( int scheduleType )
+{
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+float CHL1NPCTalker::PickLookTarget( bool bExcludePlayers, float minTime, float maxTime )
+{
+ return random->RandomFloat( 5.0f, 10.0f );
+}
+
+void CHL1NPCTalker::IdleHeadTurn( CBaseEntity *pTarget, float flDuration, float flImportance )
+{
+ // Must be able to turn our head
+ if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD))
+ return;
+
+ // If the target is invalid, or we're in a script, do nothing
+ if ( ( !pTarget ) || ( m_NPCState == NPC_STATE_SCRIPT ) )
+ return;
+
+ // Fill in a duration if we haven't specified one
+ if ( flDuration == 0.0f )
+ {
+ flDuration = random->RandomFloat( 2.0, 4.0 );
+ }
+
+ // Add a look target
+ AddLookTarget( pTarget, 1.0, flDuration );
+}
+
+void CHL1NPCTalker::SetHeadDirection( const Vector &vTargetPos, float flInterval)
+{
+#ifdef TALKER_LOOK
+ // Draw line in body, head, and eye directions
+ Vector vEyePos = EyePosition();
+ Vector vHeadDir = HeadDirection3D();
+ Vector vBodyDir = BodyDirection2D();
+
+ //UNDONE <<TODO>>
+ // currently eye dir just returns head dir, so use vTargetPos for now
+ //Vector vEyeDir; w
+ //EyeDirection3D(&vEyeDir);
+ NDebugOverlay::Line( vEyePos, vEyePos+(50*vHeadDir), 255, 0, 0, false, 0.1 );
+ NDebugOverlay::Line( vEyePos, vEyePos+(50*vBodyDir), 0, 255, 0, false, 0.1 );
+ NDebugOverlay::Line( vEyePos, vTargetPos, 0, 0, 255, false, 0.1 );
+#endif
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CHL1NPCTalker::CorpseGib( const CTakeDamageInfo &info )
+{
+ CEffectData data;
+
+ data.m_vOrigin = WorldSpaceCenter();
+ data.m_vNormal = data.m_vOrigin - info.GetDamagePosition();
+ VectorNormalize( data.m_vNormal );
+
+ data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 );
+ data.m_flScale = clamp( data.m_flScale, 1, 3 );
+
+ data.m_nMaterial = 1;
+ data.m_nHitBox = -m_iHealth;
+
+ data.m_nColor = BloodColor();
+
+ DispatchEffect( "HL1Gib", data );
+
+ CSoundEnt::InsertSound( SOUND_MEAT, GetAbsOrigin(), 256, 0.5f, this );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CHL1NPCTalker::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor, float distClear, AIMoveResult_t *pResult )
+{
+ // If we can't get through the door, try and open it
+ if ( BaseClass::OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) )
+ {
+ if ( IsMoveBlocked( *pResult ) && pMoveGoal->directTrace.vHitNormal != vec3_origin )
+ {
+ // Can't do anything if the door's locked
+ if ( !pDoor->m_bLocked && !pDoor->HasSpawnFlags(SF_DOOR_NONPCS) )
+ {
+ // Tell the door to open
+ variant_t emptyVariant;
+ pDoor->AcceptInput( "Open", this, this, emptyVariant, USE_TOGGLE );
+ *pResult = AIMR_OK;
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+// HL1 version - never return Ragdoll as the automatic schedule at the end of a
+// scripted sequence
+int CHL1NPCTalker::SelectDeadSchedule()
+{
+ // Alread dead (by animation event maybe?)
+ // Is it safe to set it to SCHED_NONE?
+ if ( m_lifeState == LIFE_DEAD )
+ return SCHED_NONE;
+
+ CleanupOnDeath();
+ return SCHED_DIE;
+}
+
+
+AI_BEGIN_CUSTOM_NPC( monster_hl1talker, CHL1NPCTalker )
+
+ DECLARE_TASK( TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS )
+
+ //=========================================================
+ // > SCHED_HL1TALKER_MOVE_AWAY_FOLLOW
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HL1TALKER_FOLLOW_MOVE_AWAY,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_TARGET_FACE"
+ " TASK_STORE_LASTPOSITION 0"
+ " TASK_MOVE_AWAY_PATH 100"
+ " TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS 100"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_PLAYER 0"
+ " TASK_SET_ACTIVITY ACT_IDLE"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_HL1TALKER_IDLE_SPEAK_WAIT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HL1TALKER_IDLE_SPEAK_WAIT,
+
+ " Tasks"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Stop and talk
+ " TASK_FACE_PLAYER 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ )
+
+ //=========================================================
+ // > SCHED_HL1TALKER_BARNACLE_HIT
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HL1TALKER_BARNACLE_HIT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_HIT"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HL1TALKER_BARNACLE_PULL"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_HL1TALKER_BARNACLE_PULL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HL1TALKER_BARNACLE_PULL,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_PULL"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_HL1TALKER_BARNACLE_CHOMP
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HL1TALKER_BARNACLE_CHOMP,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHOMP"
+ " TASK_SET_SCHEDULE SCHEDULE:SCHED_HL1TALKER_BARNACLE_CHEW"
+ ""
+ " Interrupts"
+ )
+
+ //=========================================================
+ // > SCHED_HL1TALKER_BARNACLE_CHEW
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HL1TALKER_BARNACLE_CHEW,
+
+ " Tasks"
+ " TASK_PLAY_SEQUENCE ACTIVITY:ACT_BARNACLE_CHEW"
+ )
+
+AI_END_CUSTOM_NPC()
diff --git a/game/server/hl1/hl1_npc_talker.h b/game/server/hl1/hl1_npc_talker.h
new file mode 100644
index 0000000..f3d883b
--- /dev/null
+++ b/game/server/hl1/hl1_npc_talker.h
@@ -0,0 +1,125 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Base combat character with no AI
+//
+// $Workfile: $
+// $Date: $
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef HL1TALKNPC_H
+#define HL1TALKNPC_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "soundflags.h"
+
+#include "ai_task.h"
+#include "ai_schedule.h"
+#include "ai_default.h"
+#include "ai_speech.h"
+#include "ai_basenpc.h"
+#include "ai_behavior.h"
+#include "ai_behavior_follow.h"
+#include "npc_talker.h"
+
+
+#define SF_NPC_PREDISASTER ( 1 << 16 ) // This is a predisaster scientist or barney. Influences how they speak.
+
+
+
+//=========================================================
+// Talking NPC base class
+// Used for scientists and barneys
+//=========================================================
+
+//=============================================================================
+// >> CHL1NPCTalker
+//=============================================================================
+
+class CHL1NPCTalker : public CNPCSimpleTalker
+{
+ DECLARE_CLASS( CHL1NPCTalker, CNPCSimpleTalker );
+
+public:
+ CHL1NPCTalker( void )
+ {
+ }
+
+ virtual void Precache();
+
+ void StartTask( const Task_t *pTask );
+ void RunTask( const Task_t *pTask );
+ int SelectSchedule ( void );
+ bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt);
+ bool ShouldGib( const CTakeDamageInfo &info );
+
+ int TranslateSchedule( int scheduleType );
+ void IdleHeadTurn( CBaseEntity *pTarget, float flDuration = 0.0, float flImportance = 1.0f );
+ void SetHeadDirection( const Vector &vTargetPos, float flInterval);
+ bool CorpseGib( const CTakeDamageInfo &info );
+
+ Disposition_t IRelationType( CBaseEntity *pTarget );
+
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+ void StartFollowing( CBaseEntity *pLeader );
+ void StopFollowing( void );
+ int PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener );
+
+
+ void Touch( CBaseEntity *pOther );
+
+ float PickLookTarget( bool bExcludePlayers = false, float minTime = 1.5, float maxTime = 2.5 );
+
+ bool OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor, float distClear, AIMoveResult_t *pResult );
+
+ // Hacks! HL2 has a system for avoiding the player, we don't
+ // This ensures that we fall back to the real player avoidance
+ // Essentially does the opposite of what it says
+ virtual bool ShouldPlayerAvoid( void ) { return false; }
+
+ bool IsValidSpeechTarget( int flags, CBaseEntity *pEntity );
+
+protected:
+ virtual void FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ int FIdleSpeak ( void );
+
+private:
+ virtual void DeclineFollowing( void ) {}
+
+ virtual int SelectDeadSchedule( void );
+
+public:
+
+ bool m_bInBarnacleMouth;
+
+
+ enum
+ {
+ SCHED_HL1TALKER_FOLLOW_MOVE_AWAY = BaseClass::NEXT_SCHEDULE,
+ SCHED_HL1TALKER_IDLE_SPEAK_WAIT,
+ SCHED_HL1TALKER_BARNACLE_HIT,
+ SCHED_HL1TALKER_BARNACLE_PULL,
+ SCHED_HL1TALKER_BARNACLE_CHOMP,
+ SCHED_HL1TALKER_BARNACLE_CHEW,
+
+ NEXT_SCHEDULE,
+ };
+
+ enum
+ {
+ TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS = BaseClass::NEXT_TASK,
+
+ NEXT_TASK,
+ };
+
+ DECLARE_DATADESC();
+ DEFINE_CUSTOM_AI;
+};
+
+
+
+#endif //HL1TALKNPC_H
diff --git a/game/server/hl1/hl1_npc_tentacle.cpp b/game/server/hl1/hl1_npc_tentacle.cpp
new file mode 100644
index 0000000..c4cc8d1
--- /dev/null
+++ b/game/server/hl1/hl1_npc_tentacle.cpp
@@ -0,0 +1,1012 @@
+//========= 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()
diff --git a/game/server/hl1/hl1_npc_turret.cpp b/game/server/hl1/hl1_npc_turret.cpp
new file mode 100644
index 0000000..9084b57
--- /dev/null
+++ b/game/server/hl1/hl1_npc_turret.cpp
@@ -0,0 +1,1509 @@
+//========= 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 "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 "Sprite.h"
+
+#define TURRET_SHOTS 2
+#define TURRET_RANGE (100 * 12)
+#define TURRET_SPREAD Vector( 0, 0, 0 )
+#define TURRET_TURNRATE 30 //angles per 0.1 second
+#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target
+#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target
+
+typedef enum
+{
+// TURRET_ANIM_NONE = 0,
+ TURRET_ANIM_FIRE = 0,
+ TURRET_ANIM_SPIN,
+ TURRET_ANIM_DEPLOY,
+ TURRET_ANIM_RETIRE,
+ TURRET_ANIM_DIE,
+} TURRET_ANIM;
+
+#define SF_MONSTER_TURRET_AUTOACTIVATE 32
+#define SF_MONSTER_TURRET_STARTINACTIVE 64
+
+#define TURRET_GLOW_SPRITE "sprites/flare3.vmt"
+
+#define TURRET_ORIENTATION_FLOOR 0
+#define TURRET_ORIENTATION_CEILING 1
+
+class CNPC_BaseTurret : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_BaseTurret, CAI_BaseNPC );
+public:
+ void Spawn(void);
+ virtual void Precache(void);
+ void EXPORT TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+
+ virtual void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+ virtual int OnTakeDamage( const CTakeDamageInfo &info );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+
+ Class_T Classify( void );
+
+ // Think functions
+ void EXPORT ActiveThink(void);
+ void EXPORT SearchThink(void);
+ void EXPORT AutoSearchThink(void);
+ void EXPORT TurretDeath(void);
+
+ virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; }
+ virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; }
+
+ void EXPORT Deploy(void);
+ void EXPORT Retire(void);
+
+ void EXPORT Initialize(void);
+
+ virtual void Ping(void);
+ virtual void EyeOn(void);
+ virtual void EyeOff(void);
+
+
+ void InputActivate( inputdata_t &inputdata );
+ void InputDeactivate( inputdata_t &inputdata );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+ virtual bool ShouldFadeOnDeath( void ) { return false; }
+ bool ShouldGib( const CTakeDamageInfo &info ) { return false; }
+
+ // other functions
+ void SetTurretAnim(TURRET_ANIM anim);
+ int MoveTurret(void);
+ virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { };
+
+ float m_flMaxSpin; // Max time to spin the barrel w/o a target
+ int m_iSpin;
+
+ CSprite *m_pEyeGlow;
+ int m_eyeBrightness;
+
+ int m_iDeployHeight;
+ int m_iRetractHeight;
+ int m_iMinPitch;
+
+ int m_iBaseTurnRate; // angles per second
+ float m_fTurnRate; // actual turn rate
+ int m_iOrientation; // 0 = floor, 1 = Ceiling
+ int m_iOn;
+ int m_fBeserk; // Sometimes this bitch will just freak out
+ int m_iAutoStart; // true if the turret auto deploys when a target
+ // enters its range
+
+ Vector m_vecLastSight;
+ float m_flLastSight; // Last time we saw a target
+ float m_flMaxWait; // Max time to seach w/o a target
+ int m_iSearchSpeed; // Not Used!
+
+ // movement
+ float m_flStartYaw;
+ QAngle m_vecCurAngles;
+ Vector m_vecGoalAngles;
+
+
+ float m_flPingTime; // Time until the next ping, used when searching
+ float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching
+
+ float m_flDamageTime;
+
+ int m_iAmmoType;
+
+ COutputEvent m_OnActivate;
+ COutputEvent m_OnDeactivate;
+
+ //DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+};
+
+BEGIN_DATADESC( CNPC_BaseTurret )
+
+ //FIELDS
+ DEFINE_FIELD( m_flMaxSpin, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iSpin, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_eyeBrightness, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iDeployHeight, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iRetractHeight, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iMinPitch, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_fTurnRate, FIELD_FLOAT ),
+ DEFINE_FIELD( m_iOn, FIELD_INTEGER ),
+ DEFINE_FIELD( m_fBeserk, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iAutoStart, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_vecLastSight, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flLastSight, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flStartYaw, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecCurAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ),
+
+ DEFINE_FIELD( m_flPingTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flSpinUpTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flDamageTime, FIELD_TIME ),
+ //DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+
+ //KEYFIELDS
+ DEFINE_KEYFIELD( m_flMaxWait, FIELD_FLOAT, "maxsleep" ),
+ DEFINE_KEYFIELD( m_iOrientation, FIELD_INTEGER, "orientation" ),
+ DEFINE_KEYFIELD( m_iSearchSpeed, FIELD_INTEGER, "searchspeed" ),
+ DEFINE_KEYFIELD( m_iBaseTurnRate, FIELD_INTEGER, "turnrate" ),
+
+ //Use
+ DEFINE_USEFUNC( TurretUse ),
+
+ //Thinks
+ DEFINE_THINKFUNC( ActiveThink ),
+ DEFINE_THINKFUNC( SearchThink ),
+ DEFINE_THINKFUNC( AutoSearchThink ),
+ DEFINE_THINKFUNC( TurretDeath ),
+ DEFINE_THINKFUNC( SpinDownCall ),
+ DEFINE_THINKFUNC( SpinUpCall ),
+ DEFINE_THINKFUNC( Deploy ),
+ DEFINE_THINKFUNC( Retire ),
+ DEFINE_THINKFUNC( Initialize ),
+
+ //Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
+
+ //Outputs
+ DEFINE_OUTPUT( m_OnActivate, "OnActivate"),
+ DEFINE_OUTPUT( m_OnDeactivate, "OnDeactivate"),
+
+END_DATADESC()
+
+
+void CNPC_BaseTurret::Spawn()
+{
+ Precache( );
+ SetNextThink( gpGlobals->curtime + 1 );
+ SetMoveType( MOVETYPE_FLY );
+ SetSequence( 0 );
+ SetCycle( 0 );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ m_takedamage = DAMAGE_YES;
+ AddFlag( FL_AIMTARGET );
+
+ AddFlag( FL_NPC );
+ SetUse( &CNPC_BaseTurret::TurretUse );
+
+ if (( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
+ && !( m_spawnflags & SF_MONSTER_TURRET_STARTINACTIVE ))
+ {
+ m_iAutoStart = true;
+ }
+
+ ResetSequenceInfo( );
+
+ SetBoneController(0, 0);
+ SetBoneController(1, 0);
+
+ m_flFieldOfView = VIEW_FIELD_FULL;
+
+ m_bloodColor = DONT_BLEED;
+ m_flDamageTime = 0;
+
+ if ( GetSpawnFlags() & SF_MONSTER_TURRET_STARTINACTIVE )
+ {
+ SetTurretAnim( TURRET_ANIM_RETIRE );
+ SetCycle( 0.0f );
+ m_flPlaybackRate = 0.0f;
+ }
+}
+
+
+void CNPC_BaseTurret::Precache()
+{
+ m_iAmmoType = GetAmmoDef()->Index("12mmRound");
+
+ PrecacheScriptSound( "Turret.Alert" );
+ PrecacheScriptSound( "Turret.Die" );
+ PrecacheScriptSound( "Turret.Deploy" );
+ PrecacheScriptSound( "Turret.Undeploy" );
+ PrecacheScriptSound( "Turret.Ping" );
+ PrecacheScriptSound( "Turret.Shoot" );
+}
+
+Class_T CNPC_BaseTurret::Classify( void )
+{
+ if (m_iOn || m_iAutoStart)
+ return CLASS_MACHINE;
+ return CLASS_NONE;
+}
+
+//=========================================================
+// TraceAttack - being attacked
+//=========================================================
+void CNPC_BaseTurret::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ CTakeDamageInfo ainfo = info;
+
+ if ( ptr->hitgroup == 10 )
+ {
+ // hit armor
+ if ( m_flDamageTime != gpGlobals->curtime || (random->RandomInt(0,10) < 1) )
+ {
+ g_pEffects->Ricochet( ptr->endpos, ptr->plane.normal );
+ m_flDamageTime = gpGlobals->curtime;
+ }
+
+ ainfo.SetDamage( 0.1 );// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated
+ }
+
+ if ( m_takedamage == DAMAGE_NO )
+ return;
+
+ //DevMsg( 1, "traceattack: %f\n", ainfo.GetDamage() );
+
+ AddMultiDamage( info, this );
+}
+
+//=========================================================
+// TakeDamage - take damage.
+//=========================================================
+int CNPC_BaseTurret::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ if ( m_takedamage == DAMAGE_NO )
+ return 0;
+
+ float flDamage = inputInfo.GetDamage();
+
+ if (!m_iOn)
+ flDamage /= 10.0;
+
+ m_iHealth -= flDamage;
+ if (m_iHealth <= 0)
+ {
+ m_iHealth = 0;
+ m_takedamage = DAMAGE_NO;
+ m_flDamageTime = gpGlobals->curtime;
+
+// ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place???
+
+ SetUse(NULL);
+ SetThink(&CNPC_BaseTurret::TurretDeath);
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ m_OnDeactivate.FireOutput(this, this);
+
+ return 0;
+ }
+
+ if (m_iHealth <= 10)
+ {
+ if (m_iOn)
+ {
+ m_fBeserk = 1;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ }
+ }
+ return 1;
+}
+
+int CNPC_BaseTurret::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ int retVal = 0;
+
+ if (!m_takedamage)
+ return 0;
+
+ switch( m_lifeState )
+ {
+ case LIFE_ALIVE:
+ retVal = OnTakeDamage_Alive( info );
+ if ( m_iHealth <= 0 )
+ {
+ IPhysicsObject *pPhysics = VPhysicsGetObject();
+ if ( pPhysics )
+ {
+ pPhysics->EnableCollisions( false );
+ }
+
+ Event_Killed( info );
+ Event_Dying();
+ }
+ return retVal;
+ break;
+
+ case LIFE_DYING:
+ return OnTakeDamage_Dying( info );
+
+ default:
+ case LIFE_DEAD:
+ return OnTakeDamage_Dead( info );
+ }
+}
+
+void CNPC_BaseTurret::SetTurretAnim( TURRET_ANIM anim )
+{
+ /*
+ if (GetSequence() != anim)
+ {
+ switch(anim)
+ {
+ case TURRET_ANIM_FIRE:
+ case TURRET_ANIM_SPIN:
+ if (GetSequence() != TURRET_ANIM_FIRE && GetSequence() != TURRET_ANIM_SPIN)
+ {
+ m_flCycle = 0;
+ }
+ break;
+ default:
+ m_flCycle = 0;
+ break;
+ }
+
+ SetSequence( anim );
+
+ ResetSequenceInfo( );
+
+ switch(anim)
+ {
+ case TURRET_ANIM_RETIRE:
+ m_flCycle = 255;
+ m_flPlaybackRate = -1.0; //play the animation backwards
+ break;
+ case TURRET_ANIM_DIE:
+ m_flPlaybackRate = 1.0;
+ break;
+ }
+ //ALERT(at_console, "Turret anim #%d\n", anim);
+ }
+ */
+
+ if (GetSequence() != anim)
+ {
+ SetSequence( anim );
+
+ ResetSequenceInfo( );
+
+ switch(anim)
+ {
+ case TURRET_ANIM_FIRE:
+ case TURRET_ANIM_SPIN:
+ if (GetSequence() != TURRET_ANIM_FIRE && GetSequence() != TURRET_ANIM_SPIN)
+ {
+ SetCycle( 0 );
+ }
+ break;
+ case TURRET_ANIM_RETIRE:
+ SetCycle( 1.0 );
+ m_flPlaybackRate = -1.0; //play the animation backwards
+ break;
+ case TURRET_ANIM_DIE:
+ SetCycle( 0.0 );
+ m_flPlaybackRate = 1.0;
+ break;
+ default:
+ SetCycle( 0 );
+ break;
+ }
+
+
+ }
+}
+
+//=========================================================
+// Initialize - set up the turret, initial think
+//=========================================================
+void CNPC_BaseTurret::Initialize(void)
+{
+ m_iOn = 0;
+ m_fBeserk = 0;
+ m_iSpin = 0;
+
+ SetBoneController( 0, 0 );
+ SetBoneController( 1, 0 );
+
+ if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE;
+ if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT;
+
+ QAngle angles = GetAbsAngles();
+ m_flStartYaw = angles.y;
+ if (m_iOrientation == TURRET_ORIENTATION_CEILING)
+ {
+ angles.x = 180;
+ angles.y += 180;
+ if( angles.y > 360 )
+ angles.y -= 360;
+ SetAbsAngles( angles );
+
+// pev->idealpitch = 180; //not used?
+
+ Vector view_ofs = GetViewOffset();
+ view_ofs.z = -view_ofs.z;
+ SetViewOffset( view_ofs );
+
+// pev->effects |= EF_INVLIGHT; //no need
+ }
+
+ m_vecGoalAngles.x = 0;
+
+ if (m_iAutoStart)
+ {
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+ SetThink(&CNPC_BaseTurret::AutoSearchThink);
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+ else
+ {
+ SetThink( &CBaseEntity::SUB_DoNothing );
+ }
+}
+
+//=========================================================
+// ActiveThink -
+//=========================================================
+void CNPC_BaseTurret::ActiveThink(void)
+{
+ int fAttack = 0;
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ StudioFrameAdvance( );
+
+ if ( (!m_iOn) || (GetEnemy() == NULL) )
+ {
+ SetEnemy( NULL );
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ return;
+ }
+
+ // if it's dead, look for something new
+ if ( !GetEnemy()->IsAlive() )
+ {
+ if (!m_flLastSight)
+ {
+ m_flLastSight = gpGlobals->curtime + 0.5; // continue-shooting timeout
+ }
+ else
+ {
+ if (gpGlobals->curtime > m_flLastSight)
+ {
+ SetEnemy( NULL );
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ return;
+ }
+ }
+ }
+
+ Vector vecMid = EyePosition();
+ Vector vecMidEnemy = GetEnemy()->BodyTarget(vecMid, false);
+
+ // Look for our current enemy
+ int fEnemyVisible = FInViewCone( GetEnemy() ) && FVisible( GetEnemy() );
+
+ //We want to look at the enemy's eyes so we don't jitter
+ Vector vecDirToEnemyEyes = vecMidEnemy - vecMid;
+// NDebugOverlay::Line( vecMid, vecMidEnemy, 0, 255, 0, false, 1.0 );
+
+ float flDistToEnemy = vecDirToEnemyEyes.Length();
+
+ VectorNormalize( vecDirToEnemyEyes );
+
+ QAngle vecAnglesToEnemy;
+ VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy );
+
+ // Current enmey is not visible.
+ if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE))
+ {
+ if (!m_flLastSight)
+ m_flLastSight = gpGlobals->curtime + 0.5;
+ else
+ {
+ // Should we look for a new target?
+ if (gpGlobals->curtime > m_flLastSight)
+ {
+ SetEnemy( NULL );
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ return;
+ }
+ }
+ fEnemyVisible = 0;
+ }
+ else
+ {
+ m_vecLastSight = vecMidEnemy;
+ }
+
+ Vector forward;
+ AngleVectors( m_vecCurAngles, &forward );
+
+ Vector2D vec2LOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ).AsVector2D();
+ vec2LOS.NormalizeInPlace();
+
+ float flDot = vec2LOS.Dot( forward.AsVector2D() );
+
+ if ( flDot <= 0.866 )
+ fAttack = FALSE;
+ else
+ fAttack = TRUE;
+
+ //forward
+ //NDebugOverlay::Line(vecMuzzle, vecMid + ( forward ), 255,0,0, false, 0.1);
+ //LOS
+ //NDebugOverlay::Line(vecMuzzle, vecMid + ( vecDirToEnemyEyes * 200 ), 0,0,255, false, 0.1);
+
+ // fire the gun
+ if (m_iSpin && ((fAttack) || (m_fBeserk)))
+ {
+ Shoot(vecMid, forward );
+ SetTurretAnim(TURRET_ANIM_FIRE);
+ }
+ else
+ {
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ }
+
+ //move the gun
+ if (m_fBeserk)
+ {
+ if (random->RandomInt(0,9) == 0)
+ {
+ m_vecGoalAngles.y = random->RandomFloat(0,360);
+ m_vecGoalAngles.x = random->RandomFloat(0,90) - 90 * m_iOrientation;
+
+ CTakeDamageInfo info;
+ info.SetAttacker(this);
+ info.SetInflictor(this);
+ info.SetDamage( 1 );
+ info.SetDamageType( DMG_GENERIC );
+
+ TakeDamage( info ); // don't beserk forever
+ return;
+ }
+ }
+ else if (fEnemyVisible)
+ {
+ if (vecAnglesToEnemy.y > 360)
+ vecAnglesToEnemy.y -= 360;
+
+ if (vecAnglesToEnemy.y < 0)
+ vecAnglesToEnemy.y += 360;
+
+ //ALERT(at_console, "[%.2f]", vec.x);
+
+ if (vecAnglesToEnemy.x < -180)
+ vecAnglesToEnemy.x += 360;
+
+ if (vecAnglesToEnemy.x > 180)
+ vecAnglesToEnemy.x -= 360;
+
+ // now all numbers should be in [1...360]
+ // pin to turret limitations to [-90...14]
+
+ if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
+ {
+ if (vecAnglesToEnemy.x > 90)
+ vecAnglesToEnemy.x = 90;
+ else if (vecAnglesToEnemy.x < m_iMinPitch)
+ vecAnglesToEnemy.x = m_iMinPitch;
+ }
+ else
+ {
+ if (vecAnglesToEnemy.x < -90)
+ vecAnglesToEnemy.x = -90;
+ else if (vecAnglesToEnemy.x > -m_iMinPitch)
+ vecAnglesToEnemy.x = -m_iMinPitch;
+ }
+
+ //DevMsg( 1, "->[%.2f]\n", vec.x);
+
+ m_vecGoalAngles.y = vecAnglesToEnemy.y;
+ m_vecGoalAngles.x = vecAnglesToEnemy.x;
+
+ }
+
+ SpinUpCall();
+ MoveTurret();
+}
+
+//=========================================================
+// SearchThink
+// This search function will sit with the turret deployed and look for a new target.
+// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will
+// retact.
+//=========================================================
+void CNPC_BaseTurret::SearchThink(void)
+{
+ // ensure rethink
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ if (m_flSpinUpTime == 0 && m_flMaxSpin)
+ m_flSpinUpTime = gpGlobals->curtime + m_flMaxSpin;
+
+ Ping( );
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ // If we have a target and we're still healthy
+ if (pEnemy != NULL)
+ {
+ if (!pEnemy->IsAlive() )
+ pEnemy = NULL;// Dead enemy forces a search for new one
+ }
+
+
+ // Acquire Target
+ if (pEnemy == NULL)
+ {
+ GetSenses()->Look(TURRET_RANGE);
+ pEnemy = BestEnemy();
+
+ if ( pEnemy && !FVisible( pEnemy ) )
+ pEnemy = NULL;
+ }
+
+ // If we've found a target, spin up the barrel and start to attack
+ if (pEnemy != NULL)
+ {
+ m_flLastSight = 0;
+ m_flSpinUpTime = 0;
+ SetThink(&CNPC_BaseTurret::ActiveThink);
+ }
+ else
+ {
+ // Are we out of time, do we need to retract?
+ if (gpGlobals->curtime > m_flLastSight)
+ {
+ //Before we retrace, make sure that we are spun down.
+ m_flLastSight = 0;
+ m_flSpinUpTime = 0;
+ SetThink(&CNPC_BaseTurret::Retire);
+ }
+ // should we stop the spin?
+ else if ((m_flSpinUpTime) && (gpGlobals->curtime > m_flSpinUpTime))
+ {
+ SpinDownCall();
+ }
+
+ // generic hunt for new victims
+ m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate);
+ if (m_vecGoalAngles.y >= 360)
+ m_vecGoalAngles.y -= 360;
+ MoveTurret();
+ }
+
+ SetEnemy( pEnemy );
+}
+
+//=========================================================
+// AutoSearchThink -
+//=========================================================
+void CNPC_BaseTurret::AutoSearchThink(void)
+{
+ // ensure rethink
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.3 );
+
+ // If we have a target and we're still healthy
+
+ CBaseEntity *pEnemy = GetEnemy();
+
+ if (pEnemy != NULL)
+ {
+ if (!pEnemy->IsAlive() )
+ {
+ pEnemy = NULL;
+ }
+ }
+
+ // Acquire Target
+
+ if (pEnemy == NULL)
+ {
+ GetSenses()->Look( TURRET_RANGE );
+ pEnemy = BestEnemy();
+
+ if ( pEnemy && !FVisible( pEnemy ) )
+ pEnemy = NULL;
+ }
+
+ if (pEnemy != NULL)
+ {
+ SetThink(&CNPC_BaseTurret::Deploy);
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Alert" );
+ }
+
+ SetEnemy( pEnemy );
+}
+
+extern short g_sModelIndexSmoke;
+
+//=========================================================
+// TurretDeath - I die as I have lived, beyond my means
+//=========================================================
+void CNPC_BaseTurret::TurretDeath(void)
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ if (m_lifeState != LIFE_DEAD)
+ {
+ m_lifeState = LIFE_DEAD;
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Die" );
+
+ StopSound( entindex(), "Turret.Spinup" );
+
+ if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
+ m_vecGoalAngles.x = -14;
+ else
+ m_vecGoalAngles.x = 90;//-90;
+
+ SetTurretAnim(TURRET_ANIM_DIE);
+
+ EyeOn( );
+ }
+
+ EyeOff( );
+
+ if (m_flDamageTime + random->RandomFloat( 0, 2 ) > gpGlobals->curtime)
+ {
+ // lots of smoke
+ Vector pos;
+ CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos );
+ pos.z = CollisionProp()->GetCollisionOrigin().z;
+
+ CBroadcastRecipientFilter filter;
+ te->Smoke( filter, 0.0, &pos,
+ g_sModelIndexSmoke,
+ 2.5,
+ 10 );
+ }
+
+ if (m_flDamageTime + random->RandomFloat( 0, 5 ) > gpGlobals->curtime)
+ {
+ Vector vecSrc;
+ CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecSrc );
+ g_pEffects->Sparks( vecSrc );
+ }
+
+ if (IsSequenceFinished() && !MoveTurret() && m_flDamageTime + 5 < gpGlobals->curtime)
+ {
+ m_flPlaybackRate = 0;
+ SetThink( NULL );
+ }
+}
+
+//=========================================================
+// Deploy - go active
+//=========================================================
+void CNPC_BaseTurret::Deploy(void)
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ StudioFrameAdvance( );
+
+ if (GetSequence() != TURRET_ANIM_DEPLOY)
+ {
+ m_iOn = 1;
+ SetTurretAnim(TURRET_ANIM_DEPLOY);
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Deploy" );
+ m_OnActivate.FireOutput(this, this);
+ }
+
+ if (IsSequenceFinished())
+ {
+ Vector curmins, curmaxs;
+ curmins = WorldAlignMins();
+ curmaxs = WorldAlignMaxs();
+
+ curmaxs.z = m_iDeployHeight;
+ curmins.z = -m_iDeployHeight;
+
+ SetCollisionBounds( curmins, curmaxs );
+
+ m_vecCurAngles.x = 0;
+
+ QAngle angles = GetAbsAngles();
+
+ if (m_iOrientation == TURRET_ORIENTATION_CEILING)
+ {
+ m_vecCurAngles.y = UTIL_AngleMod( angles.y + 180 );
+ }
+ else
+ {
+ m_vecCurAngles.y = UTIL_AngleMod( angles.y );
+ }
+
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ m_flPlaybackRate = 0;
+ SetThink(&CNPC_BaseTurret::SearchThink);
+ }
+
+ m_flLastSight = gpGlobals->curtime + m_flMaxWait;
+}
+
+//=========================================================
+// Retire - stop being active
+//=========================================================
+void CNPC_BaseTurret::Retire(void)
+{
+ // make the turret level
+ m_vecGoalAngles.x = 0;
+ m_vecGoalAngles.y = m_flStartYaw;
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ StudioFrameAdvance( );
+
+ EyeOff( );
+
+ if (!MoveTurret())
+ {
+ if (m_iSpin)
+ {
+ SpinDownCall();
+ }
+ else if (GetSequence() != TURRET_ANIM_RETIRE)
+ {
+ SetTurretAnim(TURRET_ANIM_RETIRE);
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Undeploy" );
+ m_OnDeactivate.FireOutput(this, this);
+ }
+ //else if (IsSequenceFinished())
+ else if( GetSequence() == TURRET_ANIM_RETIRE && GetCycle() <= 0.0 )
+ {
+ m_iOn = 0;
+ m_flLastSight = 0;
+ //SetTurretAnim(TURRET_ANIM_NONE);
+
+ Vector curmins, curmaxs;
+ curmins = WorldAlignMins();
+ curmaxs = WorldAlignMaxs();
+
+ curmaxs.z = m_iRetractHeight;
+ curmins.z = -m_iRetractHeight;
+
+ SetCollisionBounds( curmins, curmaxs );
+ if (m_iAutoStart)
+ {
+ SetThink(&CNPC_BaseTurret::AutoSearchThink);
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+ else
+ {
+ SetThink( &CBaseEntity::SUB_DoNothing );
+ }
+ }
+ }
+ else
+ {
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ }
+}
+
+//=========================================================
+// Ping - make the pinging noise every second while searching
+//=========================================================
+void CNPC_BaseTurret::Ping(void)
+{
+ if (m_flPingTime == 0)
+ m_flPingTime = gpGlobals->curtime + 1;
+ else if (m_flPingTime <= gpGlobals->curtime)
+ {
+ m_flPingTime = gpGlobals->curtime + 1;
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Ping" );
+
+ EyeOn( );
+ }
+ else if (m_eyeBrightness > 0)
+ {
+ EyeOff( );
+ }
+}
+
+//=========================================================
+// MoveTurret - handle turret rotation
+// returns 1 if the turret moved.
+//=========================================================
+int CNPC_BaseTurret::MoveTurret(void)
+{
+ int bMoved = 0;
+
+ if (m_vecCurAngles.x != m_vecGoalAngles.x)
+ {
+ float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ;
+
+ m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir;
+
+ // if we started below the goal, and now we're past, peg to goal
+ if (flDir == 1)
+ {
+ if (m_vecCurAngles.x > m_vecGoalAngles.x)
+ m_vecCurAngles.x = m_vecGoalAngles.x;
+ }
+ else
+ {
+ if (m_vecCurAngles.x < m_vecGoalAngles.x)
+ m_vecCurAngles.x = m_vecGoalAngles.x;
+ }
+
+ if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
+ SetBoneController(1, m_vecCurAngles.x);
+ else
+ SetBoneController(1, -m_vecCurAngles.x);
+
+ bMoved = 1;
+ }
+
+ if (m_vecCurAngles.y != m_vecGoalAngles.y)
+ {
+ float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ;
+ float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y);
+
+ if (flDist > 180)
+ {
+ flDist = 360 - flDist;
+ flDir = -flDir;
+ }
+ if (flDist > 30)
+ {
+ if (m_fTurnRate < m_iBaseTurnRate * 10)
+ {
+ m_fTurnRate += m_iBaseTurnRate;
+ }
+ }
+ else if (m_fTurnRate > 45)
+ {
+ m_fTurnRate -= m_iBaseTurnRate;
+ }
+ else
+ {
+ m_fTurnRate += m_iBaseTurnRate;
+ }
+
+ m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir;
+
+ if (m_vecCurAngles.y < 0)
+ m_vecCurAngles.y += 360;
+ else if (m_vecCurAngles.y >= 360)
+ m_vecCurAngles.y -= 360;
+
+ if (flDist < (0.05 * m_iBaseTurnRate))
+ m_vecCurAngles.y = m_vecGoalAngles.y;
+
+ QAngle angles = GetAbsAngles();
+
+ //ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y);
+
+ if (m_iOrientation == TURRET_ORIENTATION_FLOOR)
+ SetBoneController(0, m_vecCurAngles.y - angles.y );
+ else
+ SetBoneController(0, angles.y - 180 - m_vecCurAngles.y );
+ bMoved = 1;
+ }
+
+ if (!bMoved)
+ m_fTurnRate = m_iBaseTurnRate;
+
+ //DevMsg(1, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x,
+ // m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y);
+
+ return bMoved;
+}
+
+void CNPC_BaseTurret::TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( !ShouldToggle( useType, m_iOn ) )
+ return;
+
+ if (m_iOn)
+ {
+ SetEnemy( NULL );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ m_iAutoStart = FALSE;// switching off a turret disables autostart
+ //!!!! this should spin down first!!BUGBUG
+ SetThink(&CNPC_BaseTurret::Retire);
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 0.1 ); // turn on delay
+
+ // if the turret is flagged as an autoactivate turret, re-enable it's ability open self.
+ if ( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
+ {
+ m_iAutoStart = TRUE;
+ }
+
+ SetThink(&CNPC_BaseTurret::Deploy);
+ }
+}
+
+void CNPC_BaseTurret::InputDeactivate( inputdata_t &inputdata )
+{
+ if( m_iOn && m_lifeState == LIFE_ALIVE )
+ {
+ SetEnemy( NULL );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ m_iAutoStart = FALSE;// switching off a turret disables autostart
+ //!!!! this should spin down first!!BUGBUG
+ SetThink(&CNPC_BaseTurret::Retire);
+ }
+}
+
+void CNPC_BaseTurret::InputActivate( inputdata_t &inputdata )
+{
+ if( !m_iOn && m_lifeState == LIFE_ALIVE )
+ {
+ SetNextThink( gpGlobals->curtime + 0.1 ); // turn on delay
+
+ // if the turret is flagged as an autoactivate turret, re-enable it's ability open self.
+ if ( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE )
+ {
+ m_iAutoStart = TRUE;
+ }
+
+ SetThink(&CNPC_BaseTurret::Deploy);
+ }
+}
+
+
+//=========================================================
+// EyeOn - turn on light on the turret
+//=========================================================
+void CNPC_BaseTurret::EyeOn(void)
+{
+ if (m_pEyeGlow)
+ {
+ if (m_eyeBrightness != 255)
+ {
+ m_eyeBrightness = 255;
+ }
+ m_pEyeGlow->SetBrightness( m_eyeBrightness );
+ }
+}
+
+//=========================================================
+// EyeOn - turn off light on the turret
+//=========================================================
+void CNPC_BaseTurret::EyeOff(void)
+{
+ if (m_pEyeGlow)
+ {
+ if (m_eyeBrightness > 0)
+ {
+ m_eyeBrightness = MAX( 0, m_eyeBrightness - 30 );
+ m_pEyeGlow->SetBrightness( m_eyeBrightness );
+ }
+ }
+}
+
+void CNPC_BaseTurret::Event_Killed( const CTakeDamageInfo &info )
+{
+ BaseClass::Event_Killed( info );
+
+ SetMoveType( MOVETYPE_FLY );
+}
+
+//===
+
+class CNPC_MiniTurret : public CNPC_BaseTurret
+{
+ DECLARE_CLASS( CNPC_MiniTurret, CNPC_BaseTurret );
+
+public:
+ void Spawn( );
+ void Precache(void);
+
+ // other functions
+ void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
+};
+
+class CNPC_Turret : public CNPC_BaseTurret
+{
+ DECLARE_CLASS( CNPC_Turret, CNPC_BaseTurret );
+
+public:
+ DECLARE_DATADESC();
+
+ void Spawn(void);
+ void Precache(void);
+
+ // Think functions
+ void SpinUpCall(void);
+ void SpinDownCall(void);
+
+ // other functions
+ void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
+
+private:
+ int m_iStartSpin;
+};
+
+BEGIN_DATADESC(CNPC_Turret)
+ DEFINE_FIELD( m_iStartSpin, FIELD_INTEGER ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( monster_turret, CNPC_Turret );
+LINK_ENTITY_TO_CLASS( monster_miniturret, CNPC_MiniTurret );
+
+ConVar sk_turret_health ( "sk_turret_health","50");
+ConVar sk_miniturret_health ( "sk_miniturret_health","40");
+ConVar sk_sentry_health ( "sk_sentry_health","40");
+
+void CNPC_Turret::Spawn()
+{
+ Precache( );
+ SetModel( "models/turret.mdl" );
+ m_iHealth = sk_turret_health.GetFloat();
+ m_HackedGunPos = Vector( 0, 0, 12.75 );
+ m_flMaxSpin = TURRET_MAXSPIN;
+
+ Vector view_ofs( 0, 0, 12.75 );
+ SetViewOffset( view_ofs );
+
+ CNPC_BaseTurret::Spawn( );
+
+ m_iRetractHeight = 16;
+ m_iDeployHeight = 32;
+ m_iMinPitch = -90;
+ UTIL_SetSize(this, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight));
+
+ SetThink(&CNPC_BaseTurret::Initialize);
+
+ m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, GetAbsOrigin(), FALSE );
+ m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation );
+ m_pEyeGlow->SetAttachment( this, 2 );
+
+ m_eyeBrightness = 0;
+
+ SetNextThink( gpGlobals->curtime + 0.3 );
+}
+
+void CNPC_Turret::Precache()
+{
+ CNPC_BaseTurret::Precache( );
+ PrecacheModel ("models/turret.mdl");
+ PrecacheModel (TURRET_GLOW_SPRITE);
+
+ PrecacheModel( "sprites/xspark4.vmt" );
+
+ PrecacheScriptSound( "Turret.Shoot" );
+
+ PrecacheScriptSound( "Turret.SpinUpCall" );
+ PrecacheScriptSound( "Turret.Spinup" );
+ PrecacheScriptSound( "Turret.SpinDownCall" );
+ //precache sounds
+}
+
+void CNPC_Turret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
+{
+ CPASAttenuationFilter filter( this );
+
+ FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
+
+ EmitSound( filter, entindex(), "Turret.Shoot" );
+
+ DoMuzzleFlash();
+}
+
+void CNPC_Turret::SpinUpCall(void)
+{
+ StudioFrameAdvance( );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ // Are we already spun up? If not start the two stage process.
+ if (!m_iSpin)
+ {
+ SetTurretAnim(TURRET_ANIM_SPIN);
+ // for the first pass, spin up the the barrel
+ if (!m_iStartSpin)
+ {
+ SetNextThink( gpGlobals->curtime + 1.0 ); //spinup delay
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.SpinUpCall" );
+ m_iStartSpin = 1;
+ m_flPlaybackRate = 0.1;
+ }
+ // after the barrel is spun up, turn on the hum
+ else if (m_flPlaybackRate >= 1.0)
+ {
+ SetNextThink( gpGlobals->curtime + 0.1 );// retarget delay
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Turret.Spinup" );
+ SetThink(&CNPC_BaseTurret::ActiveThink);
+ m_iStartSpin = 0;
+ m_iSpin = 1;
+ }
+ else
+ {
+ m_flPlaybackRate += 0.075;
+ }
+ }
+
+ if (m_iSpin)
+ {
+ SetThink(&CNPC_BaseTurret::ActiveThink);
+ }
+}
+
+void CNPC_Turret::SpinDownCall(void)
+{
+ if (m_iSpin)
+ {
+ CPASAttenuationFilter filter( this );
+
+ SetTurretAnim(TURRET_ANIM_SPIN);
+
+ if ( m_flPlaybackRate == 1.0)
+ {
+ StopSound( entindex(), "Turret.Spinup" );
+ EmitSound( filter, entindex(), "Turret.SpinDownCall" );
+ }
+ m_flPlaybackRate -= 0.02;
+ if (m_flPlaybackRate <= 0)
+ {
+ m_flPlaybackRate = 0;
+ m_iSpin = 0;
+ }
+ }
+}
+
+void CNPC_MiniTurret::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/miniturret.mdl" );
+ m_iHealth = sk_miniturret_health.GetFloat();
+ m_HackedGunPos = Vector( 0, 0, 12.75 );
+ m_flMaxSpin = 0;
+
+ Vector view_ofs( 0, 0, 12.75 );
+ SetViewOffset( view_ofs );
+
+ CNPC_BaseTurret::Spawn( );
+ m_iRetractHeight = 16;
+ m_iDeployHeight = 32;
+ m_iMinPitch = -90;
+ UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));
+
+ SetThink(&CNPC_MiniTurret::Initialize);
+ SetNextThink(gpGlobals->curtime + 0.3);
+
+ if (( m_spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) && !( m_spawnflags & SF_MONSTER_TURRET_STARTINACTIVE ))
+ {
+ m_iAutoStart = true;
+ }
+}
+
+
+void CNPC_MiniTurret::Precache()
+{
+ CNPC_BaseTurret::Precache( );
+
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ PrecacheScriptSound( "Turret.Shoot" );
+
+ PrecacheModel ("models/miniturret.mdl");
+}
+
+void CNPC_MiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
+{
+ FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
+
+ CPASAttenuationFilter filter( this );
+
+ EmitSound( filter, entindex(), "Turret.Shoot" );
+
+ DoMuzzleFlash();
+}
+
+//=========================================================
+// Sentry gun - smallest turret, placed near grunt entrenchments
+//=========================================================
+class CNPC_Sentry : public CNPC_BaseTurret
+{
+ DECLARE_CLASS( CNPC_Sentry, CNPC_BaseTurret );
+
+public:
+ void Spawn( );
+ void Precache(void);
+
+ // other functions
+ void Shoot(Vector &vecSrc, Vector &vecDirToEnemy);
+ int OnTakeDamage_Alive(const CTakeDamageInfo &info);
+ void Event_Killed( const CTakeDamageInfo &info );
+ void SentryTouch( CBaseEntity *pOther );
+
+ DECLARE_DATADESC();
+
+private:
+ bool m_bStartedDeploy; //set to true when the turret begins its deploy
+};
+
+BEGIN_DATADESC( CNPC_Sentry )
+ DEFINE_ENTITYFUNC( SentryTouch ),
+ DEFINE_FIELD( m_bStartedDeploy, FIELD_BOOLEAN ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( monster_sentry, CNPC_Sentry );
+
+void CNPC_Sentry::Precache()
+{
+ BaseClass::Precache( );
+
+ m_iAmmoType = GetAmmoDef()->Index("9mmRound");
+
+ PrecacheScriptSound( "Sentry.Shoot" );
+ PrecacheScriptSound( "Sentry.Die" );
+
+ PrecacheModel ("models/sentry.mdl");
+}
+
+void CNPC_Sentry::Spawn()
+{
+ Precache( );
+ SetModel( "models/sentry.mdl" );
+ m_iHealth = sk_sentry_health.GetFloat();
+ m_HackedGunPos = Vector( 0, 0, 48 );
+
+
+ SetViewOffset( Vector(0,0,48) );
+
+ m_flMaxWait = 1E6;
+ m_flMaxSpin = 1E6;
+
+ BaseClass::Spawn();
+
+ SetSequence( TURRET_ANIM_RETIRE );
+ SetCycle( 0.0 );
+ m_flPlaybackRate = 0.0;
+
+ m_iRetractHeight = 64;
+ m_iDeployHeight = 64;
+ m_iMinPitch = -60;
+
+ UTIL_SetSize(this, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight));
+
+ SetTouch(&CNPC_Sentry::SentryTouch);
+ SetThink(&CNPC_Sentry::Initialize);
+
+ SetNextThink(gpGlobals->curtime + 0.3);
+
+ m_bStartedDeploy = false;
+}
+
+void CNPC_Sentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy)
+{
+ FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, m_iAmmoType, 1 );
+
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Sentry.Shoot" );
+
+ DoMuzzleFlash();
+}
+
+int CNPC_Sentry::OnTakeDamage_Alive(const CTakeDamageInfo &info)
+{
+ if ( m_takedamage == DAMAGE_NO )
+ return 0;
+
+ if (!m_iOn && !m_bStartedDeploy)
+ {
+ m_bStartedDeploy = true;
+ SetThink( &CNPC_Sentry::Deploy );
+ SetUse( NULL );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+
+ m_iHealth -= info.GetDamage();
+ if (m_iHealth <= 0)
+ {
+ m_iHealth = 0;
+ m_takedamage = DAMAGE_NO;
+ m_flDamageTime = gpGlobals->curtime;
+
+ SetUse(NULL);
+ SetThink(&CNPC_BaseTurret::TurretDeath); //should be SentryDeath ?
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ m_OnDeactivate.FireOutput(this, this);
+
+ return 0;
+ }
+
+ return 1;
+}
+
+void CNPC_Sentry::Event_Killed( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Sentry.Die" );
+
+ StopSound( entindex(), "Turret.Spinup" );
+
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ Vector vecSrc;
+ QAngle vecAng;
+ GetAttachment( 2, vecSrc, vecAng );
+
+ te->Smoke( filter, 0.0, &vecSrc,
+ g_sModelIndexSmoke,
+ 2.5,
+ 10 );
+
+ g_pEffects->Sparks( vecSrc );
+
+ BaseClass::Event_Killed( info );
+}
+
+
+void CNPC_Sentry::SentryTouch( CBaseEntity *pOther )
+{
+ //trigger the sentry to turn on if a monster or player touches it
+ if ( pOther && (pOther->IsPlayer() || FBitSet ( pOther->GetFlags(), FL_NPC )) )
+ {
+ CTakeDamageInfo info;
+ info.SetAttacker( pOther );
+ info.SetInflictor( pOther );
+ info.SetDamage( 0 );
+
+ TakeDamage(info);
+ }
+}
+
diff --git a/game/server/hl1/hl1_npc_vortigaunt.cpp b/game/server/hl1/hl1_npc_vortigaunt.cpp
new file mode 100644
index 0000000..a6d6283
--- /dev/null
+++ b/game/server/hl1/hl1_npc_vortigaunt.cpp
@@ -0,0 +1,743 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Alien slave monster
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "beam_shared.h"
+#include "game.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_route.h"
+#include "ai_squad.h"
+#include "npcevent.h"
+#include "gib.h"
+//#include "AI_Interactions.h"
+#include "ndebugoverlay.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "hl1_npc_vortigaunt.h"
+#include "soundent.h"
+#include "player.h"
+#include "IEffects.h"
+#include "basecombatweapon.h"
+#include "SoundEmitterSystem/isoundemittersystembase.h"
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define ISLAVE_AE_CLAW ( 1 )
+#define ISLAVE_AE_CLAWRAKE ( 2 )
+#define ISLAVE_AE_ZAP_POWERUP ( 3 )
+#define ISLAVE_AE_ZAP_SHOOT ( 4 )
+#define ISLAVE_AE_ZAP_DONE ( 5 )
+
+
+ConVar sk_islave_health( "sk_islave_health","50");
+ConVar sk_islave_dmg_claw( "sk_islave_dmg_claw","8");
+ConVar sk_islave_dmg_clawrake( "sk_islave_dmg_clawrake","25");
+ConVar sk_islave_dmg_zap( "sk_islave_dmg_zap","15");
+
+LINK_ENTITY_TO_CLASS( monster_alien_slave, CNPC_Vortigaunt );
+
+BEGIN_DATADESC( CNPC_Vortigaunt )
+ DEFINE_FIELD( m_iBravery, FIELD_INTEGER ),
+ DEFINE_ARRAY( m_pBeam, FIELD_CLASSPTR, VORTIGAUNT_MAX_BEAMS ),
+ DEFINE_FIELD( m_iBeams, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flNextAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_iVoicePitch, FIELD_INTEGER ),
+ DEFINE_FIELD( m_hDead, FIELD_EHANDLE ),
+END_DATADESC()
+
+enum
+{
+ SCHED_VORTIGAUNT_ATTACK = LAST_SHARED_SCHEDULE,
+};
+
+#define VORTIGAUNT_IGNORE_PLAYER 64
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Vortigaunt::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/islave.mdl" );
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ ClearEffects();
+ m_iHealth = sk_islave_health.GetFloat();
+ //pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin.
+ m_flFieldOfView = VIEW_FIELD_WIDE;
+ m_NPCState = NPC_STATE_NONE;
+
+ m_iVoicePitch = random->RandomInt( 85, 110 );
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND );
+ CapabilitiesAdd( bits_CAP_SQUAD );
+
+ CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP );
+
+ CapabilitiesAdd ( bits_CAP_INNATE_RANGE_ATTACK1 );
+ CapabilitiesAdd ( bits_CAP_INNATE_MELEE_ATTACK1 );
+
+ m_iBravery = 0;
+
+ NPCInit();
+
+ BaseClass::Spawn();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Vortigaunt::Precache()
+{
+ BaseClass::Precache();
+
+ PrecacheModel("models/islave.mdl");
+ PrecacheModel("sprites/lgtning.vmt");
+
+ PrecacheScriptSound( "Vortigaunt.Pain" );
+ PrecacheScriptSound( "Vortigaunt.Die" );
+ PrecacheScriptSound( "Vortigaunt.AttackHit" );
+ PrecacheScriptSound( "Vortigaunt.AttackMiss" );
+ PrecacheScriptSound( "Vortigaunt.ZapPowerup" );
+ PrecacheScriptSound( "Vortigaunt.ZapShoot" );
+}
+
+Disposition_t CNPC_Vortigaunt::IRelationType ( CBaseEntity *pTarget )
+{
+ if ( (pTarget->IsPlayer()) )
+ {
+ if ( (GetSpawnFlags() & VORTIGAUNT_IGNORE_PLAYER ) && !HasMemory( bits_MEMORY_PROVOKED ) )
+ return D_NU;
+ }
+
+ return BaseClass::IRelationType( pTarget );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+Class_T CNPC_Vortigaunt::Classify ( void )
+{
+ return CLASS_ALIEN_MILITARY;
+}
+
+void CNPC_Vortigaunt::CallForHelp( char *szClassname, float flDist, CBaseEntity * pEnemy, Vector &vecLocation )
+{
+ // ALERT( at_aiconsole, "help " );
+
+ // skip ones not on my netname
+ if ( !m_pSquad )
+ return;
+
+ AISquadIter_t iter;
+ for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
+ {
+ float d = ( GetAbsOrigin() - pSquadMember->GetAbsOrigin() ).Length();
+
+ if ( d < flDist )
+ {
+ pSquadMember->Remember( bits_MEMORY_PROVOKED );
+ pSquadMember->UpdateEnemyMemory( pEnemy, vecLocation );
+ }
+ }
+}
+
+
+//=========================================================
+// ALertSound - scream
+//=========================================================
+void CNPC_Vortigaunt::AlertSound( void )
+{
+ if ( GetEnemy() != NULL )
+ {
+ SENTENCEG_PlayRndSz( edict(), "SLV_ALERT", 0.85, SNDLVL_NORM, 0, m_iVoicePitch );
+
+ Vector vecTmp = GetEnemy()->GetAbsOrigin();
+ CallForHelp( "monster_alien_slave", 512, GetEnemy(), vecTmp );
+ }
+}
+
+//=========================================================
+// IdleSound
+//=========================================================
+void CNPC_Vortigaunt::IdleSound( void )
+{
+ if ( random->RandomInt( 0, 2 ) == 0)
+ SENTENCEG_PlayRndSz( edict(), "SLV_IDLE", 0.85, SNDLVL_NORM, 0, m_iVoicePitch);
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_Vortigaunt::PainSound( const CTakeDamageInfo &info )
+{
+ if ( random->RandomInt( 0, 2 ) == 0)
+ {
+ CPASAttenuationFilter filter( this );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "Vortigaunt.Pain", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ params.pitch = m_iVoicePitch;
+
+ EmitSound( filter, entindex(), ep );
+ }
+ }
+}
+
+//=========================================================
+// DieSound
+//=========================================================
+
+void CNPC_Vortigaunt::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ CSoundParameters params;
+ if ( GetParametersForSound( "Vortigaunt.Die", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ params.pitch = m_iVoicePitch;
+
+ EmitSound( filter, entindex(), ep );
+ }
+}
+
+int CNPC_Vortigaunt::GetSoundInterests ( void )
+{
+ return SOUND_WORLD |
+ SOUND_COMBAT |
+ SOUND_DANGER |
+ SOUND_PLAYER;
+}
+
+void CNPC_Vortigaunt::Event_Killed( const CTakeDamageInfo &info )
+{
+ ClearBeams( );
+ BaseClass::Event_Killed( info );
+}
+
+//=========================================================
+// SetYawSpeed - allows each sequence to have a different
+// turn rate associated with it.
+//=========================================================
+float CNPC_Vortigaunt::MaxYawSpeed ( void )
+{
+ float flYS;
+
+ switch ( GetActivity() )
+ {
+ case ACT_WALK:
+ flYS = 50;
+ break;
+ case ACT_RUN:
+ flYS = 70;
+ break;
+ case ACT_IDLE:
+ flYS = 50;
+ break;
+ default:
+ flYS = 90;
+ break;
+ }
+
+ return flYS;
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//
+// Returns number of events handled, 0 if none.
+//=========================================================
+void CNPC_Vortigaunt::HandleAnimEvent( animevent_t *pEvent )
+{
+ // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame );
+ switch( pEvent->event )
+ {
+ case ISLAVE_AE_CLAW:
+ {
+ // SOUND HERE!
+ CBaseEntity *pHurt = CheckTraceHullAttack( 40, Vector(-10,-10,-10), Vector(10,10,10), sk_islave_dmg_claw.GetFloat(), DMG_SLASH );
+ CPASAttenuationFilter filter( this );
+ if ( pHurt )
+ {
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ pHurt->ViewPunch( QAngle( 5, 0, -18 ) );
+
+ // Play a random attack hit sound
+ CSoundParameters params;
+ if ( GetParametersForSound( "Vortigaunt.AttackHit", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ params.pitch = m_iVoicePitch;
+
+ EmitSound( filter, entindex(), ep );
+ }
+ }
+ else
+ {
+ // Play a random attack miss sound
+ CSoundParameters params;
+ if ( GetParametersForSound( "Vortigaunt.AttackMiss", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ params.pitch = m_iVoicePitch;
+
+ EmitSound( filter, entindex(), ep );
+ }
+ }
+ }
+ break;
+
+ case ISLAVE_AE_CLAWRAKE:
+ {
+ CBaseEntity *pHurt = CheckTraceHullAttack( 40, Vector(-10,-10,-10), Vector(10,10,10), sk_islave_dmg_clawrake.GetFloat(), DMG_SLASH );
+ CPASAttenuationFilter filter2( this );
+ if ( pHurt )
+ {
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ pHurt->ViewPunch( QAngle( 5, 0, 18 ) );
+
+ CSoundParameters params;
+ if ( GetParametersForSound( "Vortigaunt.AttackHit", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ params.pitch = m_iVoicePitch;
+
+ EmitSound( filter2, entindex(), ep );
+ }
+ }
+ else
+ {
+ CSoundParameters params;
+ if ( GetParametersForSound( "Vortigaunt.AttackMiss", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ params.pitch = m_iVoicePitch;
+
+ EmitSound( filter2, entindex(), ep );
+ }
+ }
+ }
+ break;
+
+ case ISLAVE_AE_ZAP_POWERUP:
+ {
+ // speed up attack when on hard
+ if ( g_iSkillLevel == SKILL_HARD )
+ m_flPlaybackRate = 1.5;
+
+ Vector v_forward;
+ GetVectors( &v_forward, NULL, NULL );
+
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 125, 200, 100, 2, 120, 0.2 / m_flPlaybackRate, 0 );
+
+ if ( m_hDead != NULL )
+ {
+ WackBeam( -1, m_hDead );
+ WackBeam( 1, m_hDead );
+ }
+ else
+ {
+ ArmBeam( -1 );
+ ArmBeam( 1 );
+ BeamGlow( );
+ }
+
+ CPASAttenuationFilter filter3( this );
+ CSoundParameters params;
+ if ( GetParametersForSound( "Vortigaunt.ZapPowerup", params, NULL ) )
+ {
+ EmitSound_t ep( params );
+ ep.m_nPitch = 100 + m_iBeams * 10;
+ EmitSound( filter3, entindex(), ep );
+ }
+
+// Huh? Model doesn't have multiple texturegroups, commented this out. -LH
+// m_nSkin = m_iBeams / 2;
+ }
+ break;
+
+ case ISLAVE_AE_ZAP_SHOOT:
+ {
+ ClearBeams( );
+
+ if ( m_hDead != NULL )
+ {
+ Vector vecDest = m_hDead->GetAbsOrigin() + Vector( 0, 0, 38 );
+ trace_t trace;
+ UTIL_TraceHull( vecDest, vecDest, GetHullMins(), GetHullMaxs(),MASK_SOLID, m_hDead, COLLISION_GROUP_NONE, &trace );
+
+ if ( !trace.startsolid )
+ {
+ CBaseEntity *pNew = Create( "monster_alien_slave", m_hDead->GetAbsOrigin(), m_hDead->GetAbsAngles() );
+
+ pNew->AddSpawnFlags( 1 );
+ WackBeam( -1, pNew );
+ WackBeam( 1, pNew );
+ UTIL_Remove( m_hDead );
+ break;
+ }
+ }
+
+ ClearMultiDamage();
+
+ ZapBeam( -1 );
+ ZapBeam( 1 );
+
+ CPASAttenuationFilter filter4( this );
+ EmitSound( filter4, entindex(), "Vortigaunt.ZapShoot" );
+ ApplyMultiDamage();
+
+ m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 0.5, 4.0 );
+ }
+ break;
+
+ case ISLAVE_AE_ZAP_DONE:
+ {
+ ClearBeams();
+ }
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose : For innate range attack
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+int CNPC_Vortigaunt::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( GetEnemy() == NULL )
+ return( COND_LOST_ENEMY );
+
+ if ( gpGlobals->curtime < m_flNextAttack )
+ return COND_NONE;
+
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ return COND_NONE;
+
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+
+void CNPC_Vortigaunt::StartTask( const Task_t *pTask )
+{
+ ClearBeams();
+ BaseClass::StartTask( pTask );
+}
+
+//=========================================================
+// TakeDamage - get provoked when injured
+//=========================================================
+
+int CNPC_Vortigaunt::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ // don't slash one of your own
+ if ( ( inputInfo.GetDamageType() & DMG_SLASH ) && inputInfo.GetAttacker() && IRelationType( inputInfo.GetAttacker() ) == D_NU )
+ return 0;
+
+ Remember( bits_MEMORY_PROVOKED );
+
+ return BaseClass::OnTakeDamage_Alive( inputInfo );
+}
+
+
+void CNPC_Vortigaunt::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ if ( info.GetDamageType() & DMG_SHOCK )
+ return;
+
+ BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
+}
+
+//=========================================================
+//=========================================================
+int CNPC_Vortigaunt::SelectSchedule( void )
+{
+ ClearBeams();
+
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_COMBAT:
+// dead enemy
+ if ( HasCondition( COND_ENEMY_DEAD ) )
+ {
+ // call base class, all code to handle dead enemies is centralized there.
+ return BaseClass::SelectSchedule();
+ }
+
+ if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ return SCHED_RANGE_ATTACK1;
+
+ if ( m_iHealth < 20 || m_iBravery < 0)
+ {
+ if ( !HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ SetDefaultFailSchedule( SCHED_CHASE_ENEMY );
+ if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ if ( HasCondition ( COND_SEE_ENEMY ) && HasCondition ( COND_ENEMY_FACING_ME ) )
+ return SCHED_TAKE_COVER_FROM_ENEMY;
+ }
+ }
+ break;
+ }
+ return BaseClass::SelectSchedule( );
+}
+
+int CNPC_Vortigaunt::TranslateSchedule( int scheduleType )
+{
+ //Oops can't get to my enemy.
+ if ( scheduleType == SCHED_CHASE_ENEMY_FAILED )
+ {
+ return SCHED_ESTABLISH_LINE_OF_FIRE;
+ }
+
+ switch ( scheduleType )
+ {
+ case SCHED_FAIL:
+
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return ( SCHED_MELEE_ATTACK1 );
+ }
+
+ break;
+
+ case SCHED_RANGE_ATTACK1:
+ {
+ //Adrian - HACK HACK! This should've been done up there ^^^^
+ if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
+ {
+ return ( SCHED_MELEE_ATTACK1 );
+ }
+
+ return SCHED_VORTIGAUNT_ATTACK;
+ }
+
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//=========================================================
+// ArmBeam - small beam from arm to nearby geometry
+//=========================================================
+void CNPC_Vortigaunt::ArmBeam( int side )
+{
+ trace_t tr;
+ float flDist = 1.0;
+
+ if ( m_iBeams >= VORTIGAUNT_MAX_BEAMS )
+ return;
+
+ Vector forward, right, up;
+ Vector vecAim;
+ AngleVectors( GetAbsAngles(), &forward, &right, &up );
+ Vector vecSrc = GetAbsOrigin() + up * 36 + right * side * 16 + forward * 32;
+
+ for (int i = 0; i < 3; i++)
+ {
+ vecAim = right * side * random->RandomFloat( 0, 1 ) + up * random->RandomFloat( -1, 1 );
+ trace_t tr1;
+ UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 512, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr1);
+ if (flDist > tr1.fraction)
+ {
+ tr = tr1;
+ flDist = tr.fraction;
+ }
+ }
+
+ // Couldn't find anything close enough
+ if ( flDist == 1.0 )
+ return;
+
+ if( tr.m_pEnt && tr.m_pEnt->m_takedamage && !tr.m_pEnt->IsNPC() )
+ {
+ CTakeDamageInfo info( this, this, 10, DMG_SHOCK );
+ CalculateMeleeDamageForce( &info, vecAim, tr.endpos );
+
+ tr.m_pEnt->TakeDamage( info );
+ }
+
+ UTIL_DecalTrace( &tr, "FadingScorch" );
+
+ m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 3.0f );
+
+ if ( m_pBeam[m_iBeams] == NULL )
+ return;
+
+ m_pBeam[m_iBeams]->PointEntInit( tr.endpos, this );
+ m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 );
+
+ m_pBeam[m_iBeams]->SetColor( 96, 128, 16 );
+
+ m_pBeam[m_iBeams]->SetBrightness( 64 );
+ m_pBeam[m_iBeams]->SetNoise( 12.8 );
+ m_pBeam[m_iBeams]->AddSpawnFlags( SF_BEAM_TEMPORARY );
+
+ m_iBeams++;
+}
+
+//=========================================================
+// BeamGlow - brighten all beams
+//=========================================================
+void CNPC_Vortigaunt::BeamGlow( )
+{
+ int b = m_iBeams * 32;
+
+ if ( b > 255 )
+ b = 255;
+
+ for ( int i = 0; i < m_iBeams; i++ )
+ {
+ if ( m_pBeam[i] != NULL )
+ {
+ if ( m_pBeam[i]->GetBrightness() != 255 )
+ m_pBeam[i]->SetBrightness( b );
+ }
+ }
+}
+
+//=========================================================
+// WackBeam - regenerate dead colleagues
+//=========================================================
+void CNPC_Vortigaunt::WackBeam( int side, CBaseEntity *pEntity )
+{
+ Vector vecDest;
+
+ if ( m_iBeams >= VORTIGAUNT_MAX_BEAMS )
+ return;
+
+ if ( pEntity == NULL )
+ return;
+
+ m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 3.0f );
+ if ( m_pBeam[m_iBeams] == NULL )
+ return;
+
+ m_pBeam[m_iBeams]->PointEntInit( pEntity->WorldSpaceCenter(), this );
+ m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 );
+ m_pBeam[m_iBeams]->SetColor( 180, 255, 96 );
+ m_pBeam[m_iBeams]->SetBrightness( 255 );
+ m_pBeam[m_iBeams]->SetNoise( 12.8 );
+ m_pBeam[m_iBeams]->AddSpawnFlags( SF_BEAM_TEMPORARY );
+ m_iBeams++;
+}
+
+//=========================================================
+// ZapBeam - heavy damage directly forward
+//=========================================================
+void CNPC_Vortigaunt::ZapBeam( int side )
+{
+ Vector vecSrc, vecAim;
+ trace_t tr;
+ CBaseEntity *pEntity;
+
+ if ( m_iBeams >= VORTIGAUNT_MAX_BEAMS )
+ return;
+
+ Vector forward, right, up;
+ AngleVectors( GetAbsAngles(), &forward, &right, &up );
+
+ vecSrc = GetAbsOrigin() + up * 36;
+ vecAim = GetShootEnemyDir( vecSrc );
+ float deflection = 0.01;
+ vecAim = vecAim + side * right * random->RandomFloat( 0, deflection ) + up * random->RandomFloat( -deflection, deflection );
+ UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 1024, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.vmt", 5.0f );
+ if ( m_pBeam[m_iBeams] == NULL )
+ return;
+
+ m_pBeam[m_iBeams]->PointEntInit( tr.endpos, this );
+ m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 );
+ m_pBeam[m_iBeams]->SetColor( 180, 255, 96 );
+ m_pBeam[m_iBeams]->SetBrightness( 255 );
+ m_pBeam[m_iBeams]->SetNoise( 3.2f );
+ m_pBeam[m_iBeams]->AddSpawnFlags( SF_BEAM_TEMPORARY );
+ m_iBeams++;
+
+ pEntity = tr.m_pEnt;
+
+ if ( pEntity != NULL && m_takedamage )
+ {
+ CTakeDamageInfo info( this, this, sk_islave_dmg_zap.GetFloat(), DMG_SHOCK );
+ CalculateMeleeDamageForce( &info, vecAim, tr.endpos );
+ pEntity->DispatchTraceAttack( info, vecAim, &tr );
+ }
+}
+
+//=========================================================
+// ClearBeams - remove all beams
+//=========================================================
+void CNPC_Vortigaunt::ClearBeams( )
+{
+ for (int i = 0; i < VORTIGAUNT_MAX_BEAMS; i++)
+ {
+ if (m_pBeam[i])
+ {
+ UTIL_Remove( m_pBeam[i] );
+ m_pBeam[i] = NULL;
+ }
+ }
+
+ m_iBeams = 0;
+ m_nSkin = 0;
+}
+
+
+//------------------------------------------------------------------------------
+//
+// Schedules
+//
+//------------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( monster_alien_slave, CNPC_Vortigaunt )
+
+ //=========================================================
+ // > SCHED_VORTIGAUNT_ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_VORTIGAUNT_ATTACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_FACE_IDEAL 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_CAN_MELEE_ATTACK1"
+ " COND_HEAVY_DAMAGE"
+ " COND_HEAR_DANGER"
+ )
+
+AI_END_CUSTOM_NPC()
diff --git a/game/server/hl1/hl1_npc_vortigaunt.h b/game/server/hl1/hl1_npc_vortigaunt.h
new file mode 100644
index 0000000..63cdb66
--- /dev/null
+++ b/game/server/hl1/hl1_npc_vortigaunt.h
@@ -0,0 +1,74 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_VORTIGAUNT_H
+#define NPC_VORTIGAUNT_H
+
+#define VORTIGAUNT_MAX_BEAMS 8
+
+#include "hl1_ai_basenpc.h"
+//=========================================================
+//=========================================================
+class CNPC_Vortigaunt : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Vortigaunt, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ Class_T Classify ( void );
+
+ void AlertSound( void );
+ void IdleSound( void );
+ void PainSound( const CTakeDamageInfo &info );
+ void DeathSound( const CTakeDamageInfo &info );
+
+ int GetSoundInterests ( void );
+
+ float MaxYawSpeed ( void );
+
+ void Event_Killed( const CTakeDamageInfo &info );
+ void CallForHelp( char *szClassname, float flDist, CBaseEntity * pEnemy, Vector &vecLocation );
+
+ int RangeAttack1Conditions( float flDot, float flDist );
+
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+ void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
+
+ void StartTask( const Task_t *pTask );
+
+ int SelectSchedule( void );
+ int TranslateSchedule( int scheduleType );
+
+ void ArmBeam( int side );
+ void BeamGlow( void );
+ void WackBeam( int side, CBaseEntity *pEntity );
+ void ZapBeam( int side );
+ void ClearBeams( void );
+
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ virtual Disposition_t IRelationType ( CBaseEntity *pTarget );
+
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+
+private:
+ int m_iVoicePitch;
+ int m_iBeams;
+
+ int m_iBravery;
+
+ CBeam *m_pBeam[VORTIGAUNT_MAX_BEAMS];
+
+ float m_flNextAttack;
+
+ EHANDLE m_hDead;
+};
+
+
+#endif //NPC_VORTIGAUNT_ \ No newline at end of file
diff --git a/game/server/hl1/hl1_npc_zombie.cpp b/game/server/hl1/hl1_npc_zombie.cpp
new file mode 100644
index 0000000..12874bb
--- /dev/null
+++ b/game/server/hl1/hl1_npc_zombie.cpp
@@ -0,0 +1,307 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: A slow-moving, once-human headcrab victim with only melee attacks.
+//
+// UNDONE: Make head take 100% damage, body take 30% damage.
+// UNDONE: Don't flinch every time you get hit.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "game.h"
+#include "ai_default.h"
+#include "ai_schedule.h"
+#include "ai_hull.h"
+#include "ai_route.h"
+#include "npcevent.h"
+#include "hl1_npc_zombie.h"
+#include "gib.h"
+//#include "AI_Interactions.h"
+#include "ndebugoverlay.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+
+ConVar sk_zombie_health( "sk_zombie_health","50");
+ConVar sk_zombie_dmg_one_slash( "sk_zombie_dmg_one_slash", "20" );
+ConVar sk_zombie_dmg_both_slash( "sk_zombie_dmg_both_slash", "40" );
+
+
+
+LINK_ENTITY_TO_CLASS( monster_zombie, CNPC_Zombie );
+
+
+//=========================================================
+// Spawn
+//=========================================================
+void CNPC_Zombie::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/zombie.mdl" );
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ m_iHealth = sk_zombie_health.GetFloat();
+ //pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin.
+ m_flFieldOfView = 0.5;
+ m_NPCState = NPC_STATE_NONE;
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_DOORS_GROUP );
+
+ NPCInit();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Zombie::Precache()
+{
+ PrecacheModel( "models/zombie.mdl" );
+
+ PrecacheScriptSound( "Zombie.AttackHit" );
+ PrecacheScriptSound( "Zombie.AttackMiss" );
+ PrecacheScriptSound( "Zombie.Pain" );
+ PrecacheScriptSound( "Zombie.Idle" );
+ PrecacheScriptSound( "Zombie.Alert" );
+ PrecacheScriptSound( "Zombie.Attack" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns this monster's place in the relationship table.
+//-----------------------------------------------------------------------------
+Class_T CNPC_Zombie::Classify( void )
+{
+ return CLASS_ALIEN_MONSTER;
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Zombie::HandleAnimEvent( animevent_t *pEvent )
+{
+ Vector v_forward, v_right;
+ switch( pEvent->event )
+ {
+ case ZOMBIE_AE_ATTACK_RIGHT:
+ {
+ // do stuff for this event.
+ // ALERT( at_console, "Slash right!\n" );
+
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ CBaseEntity *pHurt = CheckTraceHullAttack( 70, vecMins, vecMaxs, sk_zombie_dmg_one_slash.GetFloat(), DMG_SLASH );
+ CPASAttenuationFilter filter( this );
+ if ( pHurt )
+ {
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ {
+ pHurt->ViewPunch( QAngle( 5, 0, 18 ) );
+
+ GetVectors( &v_forward, &v_right, NULL );
+
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - v_right * 100 );
+ }
+ // Play a random attack hit sound
+ EmitSound( filter, entindex(), "Zombie.AttackHit" );
+ }
+ else // Play a random attack miss sound
+ EmitSound( filter, entindex(), "Zombie.AttackMiss" );
+
+ if ( random->RandomInt( 0, 1 ) )
+ AttackSound();
+ }
+ break;
+
+ case ZOMBIE_AE_ATTACK_LEFT:
+ {
+ // do stuff for this event.
+ // ALERT( at_console, "Slash left!\n" );
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ CBaseEntity *pHurt = CheckTraceHullAttack( 70, vecMins, vecMaxs, sk_zombie_dmg_one_slash.GetFloat(), DMG_SLASH );
+
+ CPASAttenuationFilter filter2( this );
+ if ( pHurt )
+ {
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ {
+ pHurt->ViewPunch( QAngle ( 5, 0, -18 ) );
+
+ GetVectors( &v_forward, &v_right, NULL );
+
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - v_right * 100 );
+ }
+ EmitSound( filter2, entindex(), "Zombie.AttackHit" );
+ }
+ else
+ {
+ EmitSound( filter2, entindex(), "Zombie.AttackMiss" );
+ }
+
+ if ( random->RandomInt( 0,1 ) )
+ AttackSound();
+ }
+ break;
+
+ case ZOMBIE_AE_ATTACK_BOTH:
+ {
+ // do stuff for this event.
+ Vector vecMins = GetHullMins();
+ Vector vecMaxs = GetHullMaxs();
+ vecMins.z = vecMins.x;
+ vecMaxs.z = vecMaxs.x;
+
+ CBaseEntity *pHurt = CheckTraceHullAttack( 70, vecMins, vecMaxs, sk_zombie_dmg_both_slash.GetFloat(), DMG_SLASH );
+
+
+ CPASAttenuationFilter filter3( this );
+ if ( pHurt )
+ {
+ if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
+ {
+ pHurt->ViewPunch( QAngle ( 5, 0, 0 ) );
+
+ GetVectors( &v_forward, &v_right, NULL );
+ pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - v_right * 100 );
+ }
+ EmitSound( filter3, entindex(), "Zombie.AttackHit" );
+ }
+ else
+ EmitSound( filter3, entindex(),"Zombie.AttackMiss" );
+
+ if ( random->RandomInt( 0,1 ) )
+ AttackSound();
+ }
+ break;
+
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+
+static float DamageForce( const Vector &size, float damage )
+{
+ float force = damage * ((32 * 32 * 72.0) / (size.x * size.y * size.z)) * 5;
+
+ if ( force > 1000.0)
+ {
+ force = 1000.0;
+ }
+
+ return force;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pInflictor -
+// pAttacker -
+// flDamage -
+// bitsDamageType -
+// Output : int
+//-----------------------------------------------------------------------------
+int CNPC_Zombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
+{
+ CTakeDamageInfo info = inputInfo;
+
+ // Take 30% damage from bullets
+ if ( info.GetDamageType() == DMG_BULLET )
+ {
+ Vector vecDir = GetAbsOrigin() - info.GetInflictor()->WorldSpaceCenter();
+ VectorNormalize( vecDir );
+ float flForce = DamageForce( WorldAlignSize(), info.GetDamage() );
+ SetAbsVelocity( GetAbsVelocity() + vecDir * flForce );
+ info.ScaleDamage( 0.3f );
+ }
+
+ // HACK HACK -- until we fix this.
+ if ( IsAlive() )
+ PainSound( info );
+
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+void CNPC_Zombie::PainSound( const CTakeDamageInfo &info )
+{
+ if ( random->RandomInt(0,5) < 2)
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Zombie.Pain" );
+ }
+}
+
+void CNPC_Zombie::AlertSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Zombie.Alert" );
+}
+
+void CNPC_Zombie::IdleSound( void )
+{
+ // Play a random idle sound
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Zombie.Idle" );
+}
+
+void CNPC_Zombie::AttackSound( void )
+{
+ // Play a random attack sound
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Zombie.Attack" );
+}
+
+int CNPC_Zombie::MeleeAttack1Conditions ( float flDot, float flDist )
+{
+ if ( flDist > 64)
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+ else if (flDot < 0.7)
+ {
+ return 0;
+ }
+ else if (GetEnemy() == NULL)
+ {
+ return 0;
+ }
+
+ return COND_CAN_MELEE_ATTACK1;
+}
+
+void CNPC_Zombie::RemoveIgnoredConditions ( void )
+{
+ if ( GetActivity() == ACT_MELEE_ATTACK1 )
+ {
+ // Nothing stops an attacking zombie.
+ ClearCondition( COND_LIGHT_DAMAGE );
+ ClearCondition( COND_HEAVY_DAMAGE );
+ }
+
+ if (( GetActivity() == ACT_SMALL_FLINCH ) || ( GetActivity() == ACT_BIG_FLINCH ))
+ {
+ if (m_flNextFlinch < gpGlobals->curtime)
+ m_flNextFlinch = gpGlobals->curtime + ZOMBIE_FLINCH_DELAY;
+ }
+
+ BaseClass::RemoveIgnoredConditions();
+}
diff --git a/game/server/hl1/hl1_npc_zombie.h b/game/server/hl1/hl1_npc_zombie.h
new file mode 100644
index 0000000..73298a8
--- /dev/null
+++ b/game/server/hl1/hl1_npc_zombie.h
@@ -0,0 +1,52 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#ifndef NPC_ZOMBIE_H
+#define NPC_ZOMBIE_H
+
+
+#include "hl1_ai_basenpc.h"
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define ZOMBIE_AE_ATTACK_RIGHT 0x01
+#define ZOMBIE_AE_ATTACK_LEFT 0x02
+#define ZOMBIE_AE_ATTACK_BOTH 0x03
+
+#define ZOMBIE_FLINCH_DELAY 2 // at most one flinch every n secs
+
+//=========================================================
+//=========================================================
+class CNPC_Zombie : public CHL1BaseNPC
+{
+ DECLARE_CLASS( CNPC_Zombie, CHL1BaseNPC );
+public:
+
+ void Spawn( void );
+ void Precache( void );
+ float MaxYawSpeed( void ) { return 120.0f; };
+ Class_T Classify( void );
+ void HandleAnimEvent( animevent_t *pEvent );
+// int IgnoreConditions ( void );
+
+ float m_flNextFlinch;
+
+ void PainSound( const CTakeDamageInfo &info );
+ void AlertSound( void );
+ void IdleSound( void );
+ void AttackSound( void );
+
+ // No range attacks
+ BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; }
+ BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; }
+ int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo );
+
+ void RemoveIgnoredConditions ( void );
+ int MeleeAttack1Conditions ( float flDot, float flDist );
+};
+
+#endif //NPC_ZOMBIE_H \ No newline at end of file
diff --git a/game/server/hl1/hl1_player.cpp b/game/server/hl1/hl1_player.cpp
new file mode 100644
index 0000000..b47cafe
--- /dev/null
+++ b/game/server/hl1/hl1_player.cpp
@@ -0,0 +1,2110 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Player for HL1.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "hl1_player.h"
+#include "gamerules.h"
+#include "trains.h"
+#include "hl1_basecombatweapon_shared.h"
+#include "vcollide_parse.h"
+#include "in_buttons.h"
+#include "igamemovement.h"
+#include "ai_hull.h"
+#include "hl2_shareddefs.h"
+#include "info_camera_link.h"
+#include "point_camera.h"
+#include "ndebugoverlay.h"
+#include "globals.h"
+#include "ai_interactions.h"
+#include "engine/IEngineSound.h"
+#include "vphysics/player_controller.h"
+#include "vphysics/constraints.h"
+#include "predicted_viewmodel.h"
+#include "physics_saverestore.h"
+#include "gamestats.h"
+
+#define DMG_FREEZE DMG_VEHICLE
+#define DMG_SLOWFREEZE DMG_DISSOLVE
+
+// HL1_DMG_SHOWNHUD: Add DMG_VEHICLE because HL2 hijacked those bits from DMG_FREEZE, which is what they are in HL1
+// HL1_DMG_SHOWNHUD: Add DMG_DISSOLVE because HL2 hijacked those bits from DMG_SLOWFREEZE, which is what they are in HL1
+// See Halflife1 GameRules - Damage_GetShowOnHud()
+//#define HL1_DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK)
+
+// TIME BASED DAMAGE AMOUNT
+// tweak these values based on gameplay feedback:
+#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage
+#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval
+
+#define NERVEGAS_DURATION 2
+#define NERVEGAS_DAMAGE 5.0
+
+#define POISON_DURATION 5
+#define POISON_DAMAGE 2.0
+
+#define RADIATION_DURATION 2
+#define RADIATION_DAMAGE 1.0
+
+#define ACID_DURATION 2
+#define ACID_DAMAGE 5.0
+
+#define SLOWBURN_DURATION 2
+#define SLOWBURN_DAMAGE 1.0
+
+#define SLOWFREEZE_DURATION 2
+#define SLOWFREEZE_DAMAGE 1.0
+
+#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes
+#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit)
+
+ConVar player_showpredictedposition( "player_showpredictedposition", "0" );
+ConVar player_showpredictedposition_timestep( "player_showpredictedposition_timestep", "1.0" );
+
+ConVar sv_hl1_allowpickup( "sv_hl1_allowpickup", "0" );
+
+LINK_ENTITY_TO_CLASS( player, CHL1_Player );
+PRECACHE_REGISTER(player);
+
+BEGIN_DATADESC( CHL1_Player )
+ DEFINE_FIELD( m_nControlClass, FIELD_INTEGER ),
+ DEFINE_AUTO_ARRAY( m_vecMissPositions, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_nNumMissPositions, FIELD_INTEGER ),
+
+ DEFINE_FIELD( m_flIdleTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flMoveTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flLastDamageTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTargetFindTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bHasLongJump, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_nFlashBattery, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flFlashLightTime, FIELD_TIME ),
+
+ DEFINE_FIELD( m_flStartCharge, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flAmmoStartCharge, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flPlayAftershock, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextAmmoBurn, FIELD_FLOAT ),
+
+ DEFINE_PHYSPTR( m_pPullConstraint ),
+ DEFINE_FIELD( m_hPullObject, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_bIsPullingObject, FIELD_BOOLEAN ),
+
+END_DATADESC()
+
+
+IMPLEMENT_SERVERCLASS_ST( CHL1_Player, DT_HL1Player )
+ SendPropInt( SENDINFO( m_bHasLongJump ), 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nFlashBattery ), 8, SPROP_UNSIGNED ),
+ SendPropBool( SENDINFO( m_bIsPullingObject ) ),
+
+ SendPropFloat( SENDINFO( m_flStartCharge ) ),
+ SendPropFloat( SENDINFO( m_flAmmoStartCharge ) ),
+ SendPropFloat( SENDINFO( m_flPlayAftershock ) ),
+ SendPropFloat( SENDINFO( m_flNextAmmoBurn ) )
+
+END_SEND_TABLE()
+
+CHL1_Player::CHL1_Player()
+{
+ m_nNumMissPositions = 0;
+}
+
+void CHL1_Player::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "Player.FlashlightOn" );
+ PrecacheScriptSound( "Player.FlashlightOff" );
+ PrecacheScriptSound( "Player.UseTrain" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allow pre-frame adjustments on the player
+//-----------------------------------------------------------------------------
+void CHL1_Player::PreThink(void)
+{
+ if ( player_showpredictedposition.GetBool() )
+ {
+ Vector predPos;
+
+ UTIL_PredictedPosition( this, player_showpredictedposition_timestep.GetFloat(), &predPos );
+
+ NDebugOverlay::Box( predPos, NAI_Hull::Mins( GetHullType() ), NAI_Hull::Maxs( GetHullType() ), 0, 255, 0, 0, 0.01f );
+ NDebugOverlay::Line( GetAbsOrigin(), predPos, 0, 255, 0, 0, 0.01f );
+ }
+
+ int buttonsChanged;
+ buttonsChanged = m_afButtonPressed | m_afButtonReleased;
+
+ UpdatePullingObject();
+
+ g_pGameRules->PlayerThink( this );
+
+ if ( g_fGameOver || IsPlayerLockedInPlace() )
+ return; // intermission or finale
+
+ ItemPreFrame( );
+ WaterMove();
+
+ if ( g_pGameRules && g_pGameRules->FAllowFlashlight() )
+ m_Local.m_iHideHUD &= ~HIDEHUD_FLASHLIGHT;
+ else
+ m_Local.m_iHideHUD |= HIDEHUD_FLASHLIGHT;
+
+
+ // checks if new client data (for HUD and view control) needs to be sent to the client
+ UpdateClientData();
+
+ CheckTimeBasedDamage();
+
+ CheckSuitUpdate();
+
+ if (m_lifeState >= LIFE_DYING)
+ {
+ PlayerDeathThink();
+ return;
+ }
+
+ // So the correct flags get sent to client asap.
+ //
+ if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE )
+ AddFlag( FL_ONTRAIN );
+ else
+ RemoveFlag( FL_ONTRAIN );
+
+ // Train speed control
+ if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE )
+ {
+ CBaseEntity *pTrain = GetGroundEntity();
+ float vel;
+
+ if ( pTrain )
+ {
+ if ( !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) )
+ pTrain = NULL;
+ }
+
+ if ( !pTrain )
+ {
+ if ( GetActiveWeapon() && (GetActiveWeapon()->ObjectCaps() & FCAP_DIRECTIONAL_USE) )
+ {
+ m_iTrain = TRAIN_ACTIVE | TRAIN_NEW;
+
+ if ( m_nButtons & IN_FORWARD )
+ {
+ m_iTrain |= TRAIN_FAST;
+ }
+ else if ( m_nButtons & IN_BACK )
+ {
+ m_iTrain |= TRAIN_BACK;
+ }
+ else
+ {
+ m_iTrain |= TRAIN_NEUTRAL;
+ }
+ return;
+ }
+ else
+ {
+ trace_t trainTrace;
+ // Maybe this is on the other side of a level transition
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,-38),
+ MASK_PLAYERSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &trainTrace );
+
+ if ( trainTrace.fraction != 1.0 && trainTrace.m_pEnt )
+ pTrain = trainTrace.m_pEnt;
+
+
+ if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(this) )
+ {
+// Warning( "In train mode with no train!\n" );
+ m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE;
+ m_iTrain = TRAIN_NEW|TRAIN_OFF;
+ return;
+ }
+ }
+ }
+ else if ( !( GetFlags() & FL_ONGROUND ) || pTrain->HasSpawnFlags( SF_TRACKTRAIN_NOCONTROL ) || (m_nButtons & (IN_MOVELEFT|IN_MOVERIGHT) ) )
+ {
+ // Turn off the train if you jump, strafe, or the train controls go dead
+ m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE;
+ m_iTrain = TRAIN_NEW|TRAIN_OFF;
+ return;
+ }
+
+ SetAbsVelocity( vec3_origin );
+ vel = 0;
+ if ( m_afButtonPressed & IN_FORWARD )
+ {
+ vel = 1;
+ pTrain->Use( this, this, USE_SET, (float)vel );
+ }
+ else if ( m_afButtonPressed & IN_BACK )
+ {
+ vel = -1;
+ pTrain->Use( this, this, USE_SET, (float)vel );
+ }
+ else if ( m_afButtonPressed & IN_JUMP )
+ {
+ m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE;
+ m_iTrain = TRAIN_NEW|TRAIN_OFF;
+ }
+
+ if (vel)
+ {
+ m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed());
+ m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW;
+ }
+ }
+ else if (m_iTrain & TRAIN_ACTIVE)
+ {
+ m_iTrain = TRAIN_NEW; // turn off train
+ }
+
+ // THIS CODE DOESN'T SEEM TO DO ANYTHING!!!
+ // WHY IS IT STILL HERE? (sjb)
+ if (m_nButtons & IN_JUMP)
+ {
+ // If on a ladder, jump off the ladder
+ // else Jump
+ if( IsPullingObject() )
+ {
+ StopPullingObject();
+ }
+
+ Jump();
+ }
+
+ // If trying to duck, already ducked, or in the process of ducking
+ if ((m_nButtons & IN_DUCK) || (GetFlags() & FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) )
+ Duck();
+
+ //
+ // If we're not on the ground, we're falling. Update our falling velocity.
+ //
+ if ( !( GetFlags() & FL_ONGROUND ) )
+ {
+ m_Local.m_flFallVelocity = -GetAbsVelocity().z;
+ }
+
+ if ( m_afPhysicsFlags & PFLAG_ONBARNACLE )
+ {
+ SetAbsVelocity( vec3_origin );
+ }
+ // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating?
+
+ //Find targets for NPC to shoot if they decide to miss us
+ FindMissTargets();
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+Class_T CHL1_Player::Classify ( void )
+{
+ // If player controlling another entity? If so, return this class
+ if (m_nControlClass != CLASS_NONE)
+ {
+ return m_nControlClass;
+ }
+ else
+ {
+ return CLASS_PLAYER;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is a generic function (to be implemented by sub-classes) to
+// handle specific interactions between different types of characters
+// (For example the barnacle grabbing an NPC)
+// Input : Constant for the type of interaction
+// Output : true - if sub-class has a response for the interaction
+// false - if sub-class has no response
+//-----------------------------------------------------------------------------
+bool CHL1_Player::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
+{
+ if ( interactionType == g_interactionBarnacleVictimDangle )
+ {
+ TakeDamage ( CTakeDamageInfo( sourceEnt, sourceEnt, m_iHealth + ArmorValue(), DMG_SLASH | DMG_ALWAYSGIB ) );
+ return true;
+ }
+
+ if (interactionType == g_interactionBarnacleVictimReleased)
+ {
+ m_afPhysicsFlags &= ~PFLAG_ONBARNACLE;
+ SetMoveType( MOVETYPE_WALK );
+ return true;
+ }
+ else if (interactionType == g_interactionBarnacleVictimGrab)
+ {
+ m_afPhysicsFlags |= PFLAG_ONBARNACLE;
+ ClearUseEntity();
+ return true;
+ }
+ return false;
+}
+
+
+void CHL1_Player::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper)
+{
+ // Handle FL_FROZEN.
+ if ( m_afPhysicsFlags & PFLAG_ONBARNACLE )
+ {
+ ucmd->forwardmove = 0;
+ ucmd->sidemove = 0;
+ ucmd->upmove = 0;
+ }
+
+ //Update our movement information
+ if ( ( ucmd->forwardmove != 0 ) || ( ucmd->sidemove != 0 ) || ( ucmd->upmove != 0 ) )
+ {
+ m_flIdleTime -= TICK_INTERVAL * 2.0f;
+
+ if ( m_flIdleTime < 0.0f )
+ {
+ m_flIdleTime = 0.0f;
+ }
+
+ m_flMoveTime += TICK_INTERVAL;
+
+ if ( m_flMoveTime > 4.0f )
+ {
+ m_flMoveTime = 4.0f;
+ }
+ }
+ else
+ {
+ m_flIdleTime += TICK_INTERVAL;
+
+ if ( m_flIdleTime > 4.0f )
+ {
+ m_flIdleTime = 4.0f;
+ }
+
+ m_flMoveTime -= TICK_INTERVAL * 2.0f;
+
+ if ( m_flMoveTime < 0.0f )
+ {
+ m_flMoveTime = 0.0f;
+ }
+ }
+
+ //Msg("Player time: [ACTIVE: %f]\t[IDLE: %f]\n", m_flMoveTime, m_flIdleTime );
+
+ BaseClass::PlayerRunCommand( ucmd, moveHelper );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create and give the named item to the player. Then return it.
+//-----------------------------------------------------------------------------
+CBaseEntity *CHL1_Player::GiveNamedItem( const char *pszName, int iSubType )
+{
+ // If I already own this type don't create one
+ if ( Weapon_OwnsThisType(pszName, iSubType) )
+ return NULL;
+
+ // Msg( "giving %s\n", pszName );
+
+ EHANDLE pent;
+
+ pent = CreateEntityByName(pszName);
+ if ( pent == NULL )
+ {
+ Msg( "NULL Ent in GiveNamedItem!\n" );
+ return NULL;
+ }
+
+ pent->SetLocalOrigin( GetLocalOrigin() );
+ pent->AddSpawnFlags( SF_NORESPAWN );
+
+ if ( iSubType )
+ {
+ CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon*>( (CBaseEntity*)pent );
+ if ( pWeapon )
+ {
+ pWeapon->SetSubType( iSubType );
+ }
+ }
+
+ DispatchSpawn( pent );
+
+ if ( pent != NULL && !(pent->IsMarkedForDeletion()) )
+ {
+ pent->Touch( this );
+ }
+
+ return pent;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHL1_Player::StartPullingObject( CBaseEntity *pObject )
+{
+ if ( pObject->VPhysicsGetObject() == NULL || VPhysicsGetObject() == NULL )
+ {
+ return;
+ }
+
+ if( !(GetFlags()&FL_ONGROUND) )
+ {
+ //Msg("Can't grab in air!\n");
+ return;
+ }
+
+ if( GetGroundEntity() == pObject )
+ {
+ //Msg("Can't grab something you're standing on!\n");
+ return;
+ }
+
+ constraint_ballsocketparams_t ballsocket;
+ ballsocket.Defaults();
+ ballsocket.constraint.Defaults();
+ ballsocket.constraint.forceLimit = lbs2kg(1000);
+ ballsocket.constraint.torqueLimit = lbs2kg(1000);
+ ballsocket.InitWithCurrentObjectState( VPhysicsGetObject(), pObject->VPhysicsGetObject(), WorldSpaceCenter() );
+ m_pPullConstraint = physenv->CreateBallsocketConstraint( VPhysicsGetObject(), pObject->VPhysicsGetObject(), NULL, ballsocket );
+
+ m_hPullObject.Set(pObject);
+ m_bIsPullingObject = true;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHL1_Player::StopPullingObject()
+{
+ if( m_pPullConstraint )
+ {
+ physenv->DestroyConstraint( m_pPullConstraint );
+ }
+
+ m_hPullObject.Set(NULL);
+ m_pPullConstraint = NULL;
+ m_bIsPullingObject = false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHL1_Player::UpdatePullingObject()
+{
+ if( !IsPullingObject() )
+ return;
+
+ CBaseEntity *pObject= m_hPullObject.Get();
+
+ if( !pObject || !pObject->VPhysicsGetObject() )
+ {
+ // Object broke or otherwise vanished.
+ StopPullingObject();
+ return;
+ }
+
+ if( m_afButtonReleased & IN_USE )
+ {
+ // Player released +USE
+ StopPullingObject();
+ return;
+ }
+
+
+ float flMaxDistSqr = Square(PLAYER_USE_RADIUS + 1.0f);
+
+ Vector objectPos;
+ QAngle angle;
+
+ pObject->VPhysicsGetObject()->GetPosition( &objectPos, &angle );
+
+ if( !FInViewCone(objectPos) )
+ {
+ // Player turned away.
+ StopPullingObject();
+ }
+ else if( objectPos.DistToSqr(WorldSpaceCenter()) > flMaxDistSqr )
+ {
+ // Object got caught up on something and left behind
+ StopPullingObject();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets HL1specific defaults.
+//-----------------------------------------------------------------------------
+void CHL1_Player::Spawn(void)
+{
+ // In multiplayer ,this is handled in the super class
+ if ( !g_pGameRules->IsMultiplayer () )
+ SetModel( "models/player.mdl" );
+
+ BaseClass::Spawn();
+
+ //
+ // Our player movement speed is set once here. This will override the cl_xxxx
+ // cvars unless they are set to be lower than this.
+ //
+ SetMaxSpeed( 1000 );
+
+ SetDefaultFOV( 0 );
+
+ m_nFlashBattery = 99;
+ m_flFlashLightTime = 1;
+
+ m_flFieldOfView = 0.5;
+
+ StopPullingObject();
+
+ m_Local.m_iHideHUD = 0;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHL1_Player::Event_Killed( const CTakeDamageInfo &info )
+{
+ StopPullingObject();
+ BaseClass::Event_Killed(info);
+}
+
+void CHL1_Player::CheckTimeBasedDamage( void )
+{
+ int i;
+ byte bDuration = 0;
+
+ static float gtbdPrev = 0.0;
+
+ // If we don't have any time based damage return.
+ if ( !g_pGameRules->Damage_IsTimeBased( m_bitsDamageType ) )
+ return;
+
+ // only check for time based damage approx. every 2 seconds
+ if (abs(gpGlobals->curtime - m_tbdPrev) < 2.0)
+ return;
+
+ m_tbdPrev = gpGlobals->curtime;
+
+ for (i = 0; i < CDMG_TIMEBASED; i++)
+ {
+ // Make sure the damage type is really time-based.
+ // This is kind of hacky but necessary until we setup DamageType as an enum.
+ int iDamage = ( DMG_PARALYZE << i );
+ if ( !g_pGameRules->Damage_IsTimeBased( iDamage ) )
+ continue;
+
+ // make sure bit is set for damage type
+ if ( m_bitsDamageType & iDamage )
+ {
+ switch (i)
+ {
+ case itbd_Paralyze:
+ // UNDONE - flag movement as half-speed
+ bDuration = PARALYZE_DURATION;
+ break;
+ case itbd_NerveGas:
+// OnTakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC);
+ bDuration = NERVEGAS_DURATION;
+ break;
+ case itbd_PoisonRecover:
+ OnTakeDamage( CTakeDamageInfo( this, this, POISON_DAMAGE, DMG_GENERIC ) );
+ bDuration = POISON_DURATION;
+ break;
+ case itbd_Radiation:
+// OnTakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC);
+ bDuration = RADIATION_DURATION;
+ break;
+ case itbd_DrownRecover:
+ // NOTE: this hack is actually used to RESTORE health
+ // after the player has been drowning and finally takes a breath
+ if (m_idrowndmg > m_idrownrestored)
+ {
+ int idif = MIN(m_idrowndmg - m_idrownrestored, 10);
+
+ TakeHealth(idif, DMG_GENERIC);
+ m_idrownrestored += idif;
+ }
+ bDuration = 4; // get up to 5*10 = 50 points back
+ break;
+
+ case itbd_Acid:
+// OnTakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC);
+ bDuration = ACID_DURATION;
+ break;
+ case itbd_SlowBurn:
+// OnTakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC);
+ bDuration = SLOWBURN_DURATION;
+ break;
+ case itbd_SlowFreeze:
+// OnTakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC);
+ bDuration = SLOWFREEZE_DURATION;
+ break;
+ default:
+ bDuration = 0;
+ }
+
+ if (m_rgbTimeBasedDamage[i])
+ {
+ // decrement damage duration, detect when done.
+ if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0)
+ {
+ m_rgbTimeBasedDamage[i] = 0;
+ // if we're done, clear damage bits
+ m_bitsDamageType &= ~(DMG_PARALYZE << i);
+ }
+ }
+ else
+ // first time taking this damage type - init damage duration
+ m_rgbTimeBasedDamage[i] = bDuration;
+ }
+ }
+}
+
+
+class CPhysicsPlayerCallback : public IPhysicsPlayerControllerEvent
+{
+public:
+ int ShouldMoveTo( IPhysicsObject *pObject, const Vector &position )
+ {
+ CHL1_Player *pPlayer = (CHL1_Player *)pObject->GetGameData();
+ if ( pPlayer )
+ {
+ if ( pPlayer->TouchedPhysics() )
+ {
+ return 0;
+ }
+ }
+ return 1;
+ }
+};
+
+static CPhysicsPlayerCallback playerCallback;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHL1_Player::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity )
+{
+ BaseClass::InitVCollision( vecAbsOrigin, vecAbsVelocity );
+
+ // Setup the HL2 specific callback.
+ GetPhysicsController()->SetEventHandler( &playerCallback );
+}
+
+
+CHL1_Player::~CHL1_Player( void )
+{
+}
+
+extern int gEvilImpulse101;
+void CHL1_Player::CheatImpulseCommands( int iImpulse )
+{
+ if ( !sv_cheats->GetBool() )
+ {
+ return;
+ }
+
+ switch( iImpulse )
+ {
+ case 101:
+ gEvilImpulse101 = true;
+
+ GiveNamedItem( "item_suit" );
+ GiveNamedItem( "item_battery" );
+ GiveNamedItem( "weapon_crowbar" );
+ GiveNamedItem( "weapon_glock" );
+ GiveNamedItem( "ammo_9mmclip" );
+ GiveNamedItem( "weapon_shotgun" );
+ GiveNamedItem( "ammo_buckshot" );
+ GiveNamedItem( "weapon_mp5" );
+ GiveNamedItem( "ammo_9mmar" );
+ GiveNamedItem( "ammo_argrenades" );
+ GiveNamedItem( "weapon_handgrenade" );
+ GiveNamedItem( "weapon_tripmine" );
+ GiveNamedItem( "weapon_357" );
+ GiveNamedItem( "ammo_357" );
+ GiveNamedItem( "weapon_crossbow" );
+ GiveNamedItem( "ammo_crossbow" );
+ GiveNamedItem( "weapon_egon" );
+ GiveNamedItem( "weapon_gauss" );
+ GiveNamedItem( "ammo_gaussclip" );
+ GiveNamedItem( "weapon_rpg" );
+ GiveNamedItem( "ammo_rpgclip" );
+ GiveNamedItem( "weapon_satchel" );
+ GiveNamedItem( "weapon_snark" );
+ GiveNamedItem( "weapon_hornetgun" );
+
+ gEvilImpulse101 = false;
+ break;
+
+ case 0:
+ default:
+ BaseClass::CheatImpulseCommands( iImpulse );
+ }
+}
+
+void CHL1_Player::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
+{
+ int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum();
+ BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );
+ PointCameraSetupVisibility( this, area, pvs, pvssize );
+}
+
+
+#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage
+#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health
+
+
+int CHL1_Player::OnTakeDamage( const CTakeDamageInfo &inputInfo )
+{
+ // have suit diagnose the problem - ie: report damage type
+ int bitsDamage = inputInfo.GetDamageType();
+ int ffound = true;
+ int fmajor;
+ int fcritical;
+ int fTookDamage;
+ int ftrivial;
+ float flRatio;
+ float flBonus;
+ float flHealthPrev = m_iHealth;
+
+ CTakeDamageInfo info = inputInfo;
+
+ if ( info.GetDamage() > 0.0f )
+ {
+ m_flLastDamageTime = gpGlobals->curtime;
+ }
+
+ flBonus = ARMOR_BONUS;
+ flRatio = ARMOR_RATIO;
+
+ if ( ( info.GetDamageType() & DMG_BLAST ) && g_pGameRules->IsMultiplayer() )
+ {
+ // blasts damage armor more.
+ flBonus *= 2;
+ }
+
+ // Already dead
+ if ( !IsAlive() )
+ return 0;
+ // go take the damage first
+
+
+ if ( !g_pGameRules->FPlayerCanTakeDamage( this, info.GetAttacker(), info ) )
+ {
+ // Refuse the damage
+ return 0;
+ }
+
+ // keep track of amount of damage last sustained
+ m_lastDamageAmount = info.GetDamage();
+
+ // Armor.
+ if ( ArmorValue() &&
+ !(info.GetDamageType() & (DMG_FALL | DMG_DROWN | DMG_POISON)) && // armor doesn't protect against fall or drown damage!
+ !(GetFlags() & FL_GODMODE) )
+ {
+ float flNew = info.GetDamage() * flRatio;
+
+ float flArmor;
+
+ flArmor = (info.GetDamage() - flNew) * flBonus;
+
+ // Does this use more armor than we have?
+ if ( flArmor > ArmorValue() )
+ {
+ flArmor = ArmorValue();
+ flArmor *= (1/flBonus);
+ flNew = info.GetDamage() - flArmor;
+ SetArmorValue( 0 );
+ }
+ else
+ SetArmorValue( ArmorValue() - flArmor );
+
+ info.SetDamage( flNew );
+ }
+
+ // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that
+ // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc)
+ info.SetDamage( (int)info.GetDamage() );
+ fTookDamage = CBaseCombatCharacter::OnTakeDamage( info ); // Bypass CBasePlayer's
+
+ if ( fTookDamage )
+ {
+ // add to the damage total for clients, which will be sent as a single
+ // message at the end of the frame
+ // todo: remove after combining shotgun blasts?
+ if ( info.GetInflictor() && info.GetInflictor()->edict() )
+ m_DmgOrigin = info.GetInflictor()->GetAbsOrigin();
+
+ m_DmgTake += (int)info.GetDamage();
+ }
+
+ // Reset damage time countdown for each type of time based damage player just sustained
+ for (int i = 0; i < CDMG_TIMEBASED; i++)
+ {
+ // Make sure the damage type is really time-based.
+ // This is kind of hacky but necessary until we setup DamageType as an enum.
+ int iDamage = ( DMG_PARALYZE << i );
+ if ( ( info.GetDamageType() & iDamage ) && g_pGameRules->Damage_IsTimeBased( iDamage ) )
+ {
+ m_rgbTimeBasedDamage[i] = 0;
+ }
+ }
+
+ // Display any effect associate with this damage type
+ DamageEffect(info.GetDamage(),bitsDamage);
+
+ // how bad is it, doc?
+ ftrivial = (m_iHealth > 75 || m_lastDamageAmount < 5);
+ fmajor = (m_lastDamageAmount > 25);
+ fcritical = (m_iHealth < 30);
+
+ // handle all bits set in this damage message,
+ // let the suit give player the diagnosis
+
+ // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash )
+
+ // UNDONE: still need to record damage and heal messages for the following types
+
+ // DMG_BURN
+ // DMG_FREEZE
+ // DMG_BLAST
+ // DMG_SHOCK
+
+ m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client
+ m_bitsHUDDamage = -1; // make sure the damage bits get resent
+
+ bool bTimeBasedDamage = g_pGameRules->Damage_IsTimeBased( bitsDamage );
+ while (fTookDamage && (!ftrivial || bTimeBasedDamage) && ffound && bitsDamage)
+ {
+ ffound = false;
+
+ if (bitsDamage & DMG_CLUB)
+ {
+ if (fmajor)
+ SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture
+ bitsDamage &= ~DMG_CLUB;
+ ffound = true;
+ }
+ if (bitsDamage & (DMG_FALL | DMG_CRUSH))
+ {
+ if (fmajor)
+ SetSuitUpdate("!HEV_DMG5", false, SUIT_NEXT_IN_30SEC); // major fracture
+ else
+ SetSuitUpdate("!HEV_DMG4", false, SUIT_NEXT_IN_30SEC); // minor fracture
+
+ bitsDamage &= ~(DMG_FALL | DMG_CRUSH);
+ ffound = true;
+ }
+
+ if (bitsDamage & DMG_BULLET)
+ {
+ if (m_lastDamageAmount > 5)
+ SetSuitUpdate("!HEV_DMG6", false, SUIT_NEXT_IN_30SEC); // blood loss detected
+ //else
+ // SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration
+
+ bitsDamage &= ~DMG_BULLET;
+ ffound = true;
+ }
+
+ if (bitsDamage & DMG_SLASH)
+ {
+ if (fmajor)
+ SetSuitUpdate("!HEV_DMG1", false, SUIT_NEXT_IN_30SEC); // major laceration
+ else
+ SetSuitUpdate("!HEV_DMG0", false, SUIT_NEXT_IN_30SEC); // minor laceration
+
+ bitsDamage &= ~DMG_SLASH;
+ ffound = true;
+ }
+
+ if (bitsDamage & DMG_SONIC)
+ {
+ if (fmajor)
+ SetSuitUpdate("!HEV_DMG2", false, SUIT_NEXT_IN_1MIN); // internal bleeding
+ bitsDamage &= ~DMG_SONIC;
+ ffound = true;
+ }
+
+ if (bitsDamage & (DMG_POISON | DMG_PARALYZE))
+ {
+ if (bitsDamage & DMG_POISON)
+ {
+ m_nPoisonDmg += info.GetDamage();
+ m_tbdPrev = gpGlobals->curtime;
+ m_rgbTimeBasedDamage[itbd_PoisonRecover] = 0;
+ }
+
+ SetSuitUpdate("!HEV_DMG3", false, SUIT_NEXT_IN_1MIN); // blood toxins detected
+ bitsDamage &= ~( DMG_POISON | DMG_PARALYZE );
+ ffound = true;
+ }
+
+ if (bitsDamage & DMG_ACID)
+ {
+ SetSuitUpdate("!HEV_DET1", false, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected
+ bitsDamage &= ~DMG_ACID;
+ ffound = true;
+ }
+
+ if (bitsDamage & DMG_NERVEGAS)
+ {
+ SetSuitUpdate("!HEV_DET0", false, SUIT_NEXT_IN_1MIN); // biohazard detected
+ bitsDamage &= ~DMG_NERVEGAS;
+ ffound = true;
+ }
+
+ if (bitsDamage & DMG_RADIATION)
+ {
+ SetSuitUpdate("!HEV_DET2", false, SUIT_NEXT_IN_1MIN); // radiation detected
+ bitsDamage &= ~DMG_RADIATION;
+ ffound = true;
+ }
+ if (bitsDamage & DMG_SHOCK)
+ {
+ bitsDamage &= ~DMG_SHOCK;
+ ffound = true;
+ }
+ }
+
+ m_Local.m_vecPunchAngle.SetX( -2 );
+
+ if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75)
+ {
+ // first time we take major damage...
+ // turn automedic on if not on
+ SetSuitUpdate("!HEV_MED1", false, SUIT_NEXT_IN_30MIN); // automedic on
+
+ // give morphine shot if not given recently
+ SetSuitUpdate("!HEV_HEAL7", false, SUIT_NEXT_IN_30MIN); // morphine shot
+ }
+
+ if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75)
+ {
+
+ // already took major damage, now it's critical...
+ if (m_iHealth < 6)
+ SetSuitUpdate("!HEV_HLTH3", false, SUIT_NEXT_IN_10MIN); // near death
+ else if (m_iHealth < 20)
+ SetSuitUpdate("!HEV_HLTH2", false, SUIT_NEXT_IN_10MIN); // health critical
+
+ // give critical health warnings
+ if (!random->RandomInt(0,3) && flHealthPrev < 50)
+ SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention
+ }
+
+ // if we're taking time based damage, warn about its continuing effects
+ if (fTookDamage && g_pGameRules->Damage_IsTimeBased( info.GetDamageType() ) && flHealthPrev < 75)
+ {
+ if (flHealthPrev < 50)
+ {
+ if (!random->RandomInt(0,3))
+ SetSuitUpdate("!HEV_DMG7", false, SUIT_NEXT_IN_5MIN); //seek medical attention
+ }
+ else
+ SetSuitUpdate("!HEV_HLTH1", false, SUIT_NEXT_IN_10MIN); // health dropping
+ }
+
+ // Do special explosion damage effect
+ if ( bitsDamage & DMG_BLAST )
+ {
+ OnDamagedByExplosion( info );
+ }
+
+ gamestats->Event_PlayerDamage( this, info );
+
+ return fTookDamage;
+}
+
+
+int CHL1_Player::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ int nRet;
+ int nSavedHealth = m_iHealth;
+
+ // Drown
+ if( info.GetDamageType() & DMG_DROWN )
+ {
+ if( m_idrowndmg == m_idrownrestored )
+ {
+ EmitSound( "Player.DrownStart" );
+ }
+ else
+ {
+ EmitSound( "Player.DrownContinue" );
+ }
+ }
+
+ nRet = BaseClass::OnTakeDamage_Alive( info );
+
+ if ( GetFlags() & FL_GODMODE )
+ {
+ m_iHealth = nSavedHealth;
+ }
+ else if (m_debugOverlays & OVERLAY_BUDDHA_MODE)
+ {
+ if ( m_iHealth <= 0 )
+ {
+ m_iHealth = 1;
+ }
+ }
+
+ return nRet;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHL1_Player::FindMissTargets( void )
+{
+ if ( m_flTargetFindTime > gpGlobals->curtime )
+ return;
+
+ m_flTargetFindTime = gpGlobals->curtime + 1.0f;
+ m_nNumMissPositions = 0;
+
+ CBaseEntity *pEnts[256];
+ Vector radius( 80, 80, 80);
+
+ int numEnts = UTIL_EntitiesInBox( pEnts, 256, GetAbsOrigin()-radius, GetAbsOrigin()+radius, 0 );
+
+ for ( int i = 0; i < numEnts; i++ )
+ {
+ if ( pEnts[i] == NULL )
+ continue;
+
+ if ( m_nNumMissPositions >= 16 )
+ return;
+
+ //See if it's a good target candidate
+ if ( FClassnameIs( pEnts[i], "prop_dynamic" ) ||
+ FClassnameIs( pEnts[i], "dynamic_prop" ) ||
+ FClassnameIs( pEnts[i], "prop_physics" ) ||
+ FClassnameIs( pEnts[i], "physics_prop" ) )
+ {
+ //NDebugOverlay::Cross3D( pEnts[i]->WorldSpaceCenter(), -Vector(4,4,4), Vector(4,4,4), 0, 255, 0, true, 1.0f );
+
+ m_vecMissPositions[m_nNumMissPositions++] = pEnts[i]->WorldSpaceCenter();
+ continue;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Good position to shoot at
+//-----------------------------------------------------------------------------
+bool CHL1_Player::GetMissPosition( Vector *position )
+{
+ if ( m_nNumMissPositions == 0 )
+ return false;
+
+ (*position) = m_vecMissPositions[ random->RandomInt( 0, m_nNumMissPositions-1 ) ];
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CHL1_Player::FlashlightIsOn( void )
+{
+ return IsEffectActive( EF_DIMLIGHT);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHL1_Player::FlashlightTurnOn( void )
+{
+ if ( IsSuitEquipped() )
+ {
+ AddEffects( EF_DIMLIGHT );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Player.FlashlightOn" );
+
+ m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->curtime;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CHL1_Player::FlashlightTurnOff( void )
+{
+ RemoveEffects( EF_DIMLIGHT );
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Player.FlashlightOff" );
+ m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->curtime;
+}
+
+
+void CHL1_Player::UpdateClientData( void )
+{
+ if (m_DmgTake || m_DmgSave || m_bitsHUDDamage != m_bitsDamageType)
+ {
+ // Comes from inside me if not set
+ Vector damageOrigin = GetLocalOrigin();
+ // send "damage" message
+ // causes screen to flash, and pain compass to show direction of damage
+ damageOrigin = m_DmgOrigin;
+
+ // only send down damage type that have hud art
+ int iShowHudDamage = g_pGameRules->Damage_GetShowOnHud();
+ int visibleDamageBits = m_bitsDamageType & iShowHudDamage;
+
+ m_DmgTake = clamp( m_DmgTake, 0, 255 );
+ m_DmgSave = clamp( m_DmgSave, 0, 255 );
+
+ CSingleUserRecipientFilter user( this );
+ user.MakeReliable();
+ UserMessageBegin( user, "Damage" );
+ WRITE_BYTE( m_DmgSave );
+ WRITE_BYTE( m_DmgTake );
+ WRITE_LONG( visibleDamageBits );
+ WRITE_FLOAT( damageOrigin.x ); //BUG: Should be fixed point (to hud) not floats
+ WRITE_FLOAT( damageOrigin.y ); //BUG: However, the HUD does _not_ implement bitfield messages (yet)
+ WRITE_FLOAT( damageOrigin.z ); //BUG: We use WRITE_VEC3COORD for everything else
+ MessageEnd();
+
+ m_DmgTake = 0;
+ m_DmgSave = 0;
+ m_bitsHUDDamage = m_bitsDamageType;
+
+ // Clear off non-time-based damage indicators
+ int iDamage = g_pGameRules->Damage_GetTimeBased();
+ m_bitsDamageType &= iDamage;
+ }
+
+ // Update Flashlight
+ if ( ( m_flFlashLightTime ) && ( m_flFlashLightTime <= gpGlobals->curtime ) )
+ {
+ if ( FlashlightIsOn() )
+ {
+ if ( m_nFlashBattery )
+ {
+ m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->curtime;
+ m_nFlashBattery--;
+
+ if ( !m_nFlashBattery )
+ FlashlightTurnOff();
+ }
+ }
+ else
+ {
+ if ( m_nFlashBattery < 100 )
+ {
+ m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->curtime;
+ m_nFlashBattery++;
+ }
+ else
+ m_flFlashLightTime = 0;
+ }
+ }
+
+ BaseClass::UpdateClientData();
+}
+
+void CHL1_Player::OnSave( IEntitySaveUtils *pUtils )
+{
+ // If I'm pulling a box, stop.
+ StopPullingObject();
+
+ BaseClass::OnSave(pUtils);
+}
+
+void CHL1_Player::CreateViewModel( int index /*=0*/ )
+{
+ Assert( index >= 0 && index < MAX_VIEWMODELS );
+
+ if ( GetViewModel( index ) )
+ return;
+
+ CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" );
+ if ( vm )
+ {
+ vm->SetAbsOrigin( GetAbsOrigin() );
+ vm->SetOwner( this );
+ vm->SetIndex( index );
+ DispatchSpawn( vm );
+ vm->FollowEntity( this );
+ m_hViewModel.Set( index, vm );
+ }
+}
+
+void CHL1_Player::OnRestore( void )
+{
+ BaseClass::OnRestore();
+
+ // If we are controlling a train, resend our train status
+ if( !FBitSet( m_iTrain, TRAIN_OFF ) )
+ {
+ m_iTrain |= TRAIN_NEW;
+ }
+}
+
+
+//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
+static void MatrixOrthogonalize( matrix3x4_t &matrix, int column )
+{
+ Vector columns[3];
+ int i;
+
+ for ( i = 0; i < 3; i++ )
+ {
+ MatrixGetColumn( matrix, i, columns[i] );
+ }
+
+ int index0 = column;
+ int index1 = (column+1)%3;
+ int index2 = (column+2)%3;
+
+ columns[index2] = CrossProduct( columns[index0], columns[index1] );
+ columns[index1] = CrossProduct( columns[index2], columns[index0] );
+ VectorNormalize( columns[index2] );
+ VectorNormalize( columns[index1] );
+ MatrixSetColumn( columns[index1], index1, matrix );
+ MatrixSetColumn( columns[index2], index2, matrix );
+}
+
+#define SIGN(x) ( (x) < 0 ? -1 : 1 )
+
+static QAngle AlignAngles( const QAngle &angles, float cosineAlignAngle )
+{
+ matrix3x4_t alignMatrix;
+ AngleMatrix( angles, alignMatrix );
+
+ for ( int j = 0; j < 3; j++ )
+ {
+ Vector vec;
+ MatrixGetColumn( alignMatrix, j, vec );
+ for ( int i = 0; i < 3; i++ )
+ {
+ if ( fabs(vec[i]) > cosineAlignAngle )
+ {
+ vec[i] = SIGN(vec[i]);
+ vec[(i+1)%3] = 0;
+ vec[(i+2)%3] = 0;
+ MatrixSetColumn( vec, j, alignMatrix );
+ MatrixOrthogonalize( alignMatrix, j );
+ break;
+ }
+ }
+ }
+
+ QAngle out;
+ MatrixAngles( alignMatrix, out );
+ return out;
+}
+
+
+static void TraceCollideAgainstBBox( const CPhysCollide *pCollide, const Vector &start, const Vector &end, const QAngle &angles, const Vector &boxOrigin, const Vector &mins, const Vector &maxs, trace_t *ptr )
+{
+ physcollision->TraceBox( boxOrigin, boxOrigin + (start-end), mins, maxs, pCollide, start, angles, ptr );
+
+ if ( ptr->DidHit() )
+ {
+ ptr->endpos = start * (1-ptr->fraction) + end * ptr->fraction;
+ ptr->startpos = start;
+ ptr->plane.dist = -ptr->plane.dist;
+ ptr->plane.normal *= -1;
+ }
+}
+
+//---------------------------------------------
+
+#include "player_pickup.h"
+#include "props.h"
+#include "vphysics/friction.h"
+#include "physics_saverestore.h"
+ConVar hl2_normspeed( "hl2_normspeed", "190" );
+ConVar player_throwforce( "player_throwforce", "1000" );
+ConVar physcannon_maxmass( "physcannon_maxmass", "250" );
+
+// derive from this so we can add save/load data to it
+struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t
+{
+ DECLARE_SIMPLE_DATADESC();
+};
+
+BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t )
+
+ DEFINE_FIELD( targetPosition, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( targetRotation, FIELD_VECTOR ),
+ DEFINE_FIELD( maxAngular, FIELD_FLOAT ),
+ DEFINE_FIELD( maxDampAngular, FIELD_FLOAT ),
+ DEFINE_FIELD( maxSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( maxDampSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( dampFactor, FIELD_FLOAT ),
+ DEFINE_FIELD( teleportDistance, FIELD_FLOAT ),
+
+END_DATADESC()
+
+// when looking level, hold bottom of object 8 inches below eye level
+#define PLAYER_HOLD_LEVEL_EYES -8
+
+// when looking down, hold bottom of object 0 inches from feet
+#define PLAYER_HOLD_DOWN_FEET 2
+
+// when looking up, hold bottom of object 24 inches above eye level
+#define PLAYER_HOLD_UP_EYES 24
+
+// use a +/-30 degree range for the entire range of motion of pitch
+#define PLAYER_LOOK_PITCH_RANGE 30
+
+// player can reach down 2ft below his feet (otherwise he'll hold the object above the bottom)
+#define PLAYER_REACH_DOWN_DISTANCE 24
+
+static void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out )
+{
+ if ( !pPlayer )
+ return;
+
+ QAngle angles = pPlayer->EyeAngles();
+ Vector origin = pPlayer->EyePosition();
+
+ // 0-360 / -180-180
+ //angles.x = init ? 0 : AngleDistance( angles.x, 0 );
+ //angles.x = clamp( angles.x, -PLAYER_LOOK_PITCH_RANGE, PLAYER_LOOK_PITCH_RANGE );
+ angles.x = 0;
+
+ float feet = pPlayer->GetAbsOrigin().z + pPlayer->WorldAlignMins().z;
+ float eyes = origin.z;
+ float zoffset = 0;
+ // moving up (negative pitch is up)
+ if ( angles.x < 0 )
+ {
+ zoffset = RemapVal( angles.x, 0, -PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_UP_EYES );
+ }
+ else
+ {
+ zoffset = RemapVal( angles.x, 0, PLAYER_LOOK_PITCH_RANGE, PLAYER_HOLD_LEVEL_EYES, PLAYER_HOLD_DOWN_FEET + (feet - eyes) );
+ }
+ origin.z += zoffset;
+ angles.x = 0;
+ AngleMatrix( angles, origin, out );
+}
+
+//-----------------------------------------------------------------------------
+class CGrabController : public IMotionEvent
+{
+ DECLARE_SIMPLE_DATADESC();
+
+public:
+
+ CGrabController( void );
+ ~CGrabController( void );
+ void AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon = false );
+ void DetachEntity();
+ void OnRestore();
+
+ bool UpdateObject( CBasePlayer *pPlayer, float flError );
+
+ void SetTargetPosition( const Vector &target, const QAngle &targetOrientation );
+ float ComputeError();
+ float GetLoadWeight( void ) const { return m_flLoadWeight; }
+ void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; }
+ void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; }
+ QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer );
+ QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer );
+
+ CBaseEntity *GetAttached() { return (CBaseEntity *)m_attachedEntity; }
+
+ IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
+ float GetSavedMass( IPhysicsObject *pObject );
+
+private:
+ // Compute the max speed for an attached object
+ void ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics );
+
+ game_shadowcontrol_params_t m_shadow;
+ float m_timeToArrive;
+ float m_errorTime;
+ float m_error;
+ float m_contactAmount;
+ float m_angleAlignment;
+ bool m_bCarriedEntityBlocksLOS;
+ bool m_bIgnoreRelativePitch;
+
+ float m_flLoadWeight;
+ float m_savedRotDamping[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ float m_savedMass[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ EHANDLE m_attachedEntity;
+ QAngle m_vecPreferredCarryAngles;
+ bool m_bHasPreferredCarryAngles;
+
+ QAngle m_attachedAnglesPlayerSpace;
+ Vector m_attachedPositionObjectSpace;
+
+ IPhysicsMotionController *m_controller;
+
+ friend class CWeaponPhysCannon;
+};
+
+BEGIN_SIMPLE_DATADESC( CGrabController )
+
+ DEFINE_EMBEDDED( m_shadow ),
+
+ DEFINE_FIELD( m_timeToArrive, FIELD_FLOAT ),
+ DEFINE_FIELD( m_errorTime, FIELD_FLOAT ),
+ DEFINE_FIELD( m_error, FIELD_FLOAT ),
+ DEFINE_FIELD( m_contactAmount, FIELD_FLOAT ),
+ DEFINE_AUTO_ARRAY( m_savedRotDamping, FIELD_FLOAT ),
+ DEFINE_AUTO_ARRAY( m_savedMass, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flLoadWeight, FIELD_FLOAT ),
+ DEFINE_FIELD( m_bCarriedEntityBlocksLOS, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bIgnoreRelativePitch, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_attachedEntity, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_angleAlignment, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecPreferredCarryAngles, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bHasPreferredCarryAngles, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_attachedAnglesPlayerSpace, FIELD_VECTOR ),
+ DEFINE_FIELD( m_attachedPositionObjectSpace, FIELD_VECTOR ),
+
+ // Physptrs can't be inside embedded classes
+ // DEFINE_PHYSPTR( m_controller ),
+
+END_DATADESC()
+
+const float DEFAULT_MAX_ANGULAR = 360.0f * 10.0f;
+const float REDUCED_CARRY_MASS = 1.0f;
+
+CGrabController::CGrabController( void )
+{
+ m_shadow.dampFactor = 1.0;
+ m_shadow.teleportDistance = 0;
+ m_errorTime = 0;
+ m_error = 0;
+ // make this controller really stiff!
+ m_shadow.maxSpeed = 1000;
+ m_shadow.maxAngular = DEFAULT_MAX_ANGULAR;
+ m_shadow.maxDampSpeed = m_shadow.maxSpeed*2;
+ m_shadow.maxDampAngular = m_shadow.maxAngular;
+ m_attachedEntity = NULL;
+ m_vecPreferredCarryAngles = vec3_angle;
+ m_bHasPreferredCarryAngles = false;
+}
+
+CGrabController::~CGrabController( void )
+{
+ DetachEntity();
+}
+
+void CGrabController::OnRestore()
+{
+ if ( m_controller )
+ {
+ m_controller->SetEventHandler( this );
+ }
+}
+
+void CGrabController::SetTargetPosition( const Vector &target, const QAngle &targetOrientation )
+{
+ m_shadow.targetPosition = target;
+ m_shadow.targetRotation = targetOrientation;
+
+ m_timeToArrive = gpGlobals->frametime;
+
+ CBaseEntity *pAttached = GetAttached();
+ if ( pAttached )
+ {
+ IPhysicsObject *pObj = pAttached->VPhysicsGetObject();
+
+ if ( pObj != NULL )
+ {
+ pObj->Wake();
+ }
+ else
+ {
+ DetachEntity();
+ }
+ }
+}
+
+float CGrabController::ComputeError()
+{
+ if ( m_errorTime <= 0 )
+ return 0;
+
+ CBaseEntity *pAttached = GetAttached();
+ if ( pAttached )
+ {
+ Vector pos;
+ IPhysicsObject *pObj = pAttached->VPhysicsGetObject();
+ if ( pObj )
+ {
+ pObj->GetShadowPosition( &pos, NULL );
+ float error = (m_shadow.targetPosition - pos).Length();
+ if ( m_errorTime > 0 )
+ {
+ if ( m_errorTime > 1 )
+ {
+ m_errorTime = 1;
+ }
+ float speed = error / m_errorTime;
+ if ( speed > m_shadow.maxSpeed )
+ {
+ error *= 0.5;
+ }
+ m_error = (1-m_errorTime) * m_error + error * m_errorTime;
+ }
+ }
+ else
+ {
+ DevMsg( "Object attached to Physcannon has no physics object\n" );
+ DetachEntity();
+ return 9999; // force detach
+ }
+ }
+
+ if ( pAttached->IsEFlagSet( EFL_IS_BEING_LIFTED_BY_BARNACLE ) )
+ {
+ m_error *= 3.0f;
+ }
+
+ m_errorTime = 0;
+
+ return m_error;
+}
+
+
+#define MASS_SPEED_SCALE 60
+#define MAX_MASS 40
+
+
+void CGrabController::ComputeMaxSpeed( CBaseEntity *pEntity, IPhysicsObject *pPhysics )
+{
+ m_shadow.maxSpeed = 1000;
+ m_shadow.maxAngular = DEFAULT_MAX_ANGULAR;
+
+ // Compute total mass...
+ float flMass = PhysGetEntityMass( pEntity );
+ float flMaxMass = physcannon_maxmass.GetFloat();
+ if ( flMass <= flMaxMass )
+ return;
+
+ float flLerpFactor = clamp( flMass, flMaxMass, 500.0f );
+ flLerpFactor = SimpleSplineRemapVal( flLerpFactor, flMaxMass, 500.0f, 0.0f, 1.0f );
+
+ float invMass = pPhysics->GetInvMass();
+ float invInertia = pPhysics->GetInvInertia().Length();
+
+ float invMaxMass = 1.0f / MAX_MASS;
+ float ratio = invMaxMass / invMass;
+ invMass = invMaxMass;
+ invInertia *= ratio;
+
+ float maxSpeed = invMass * MASS_SPEED_SCALE * 200;
+ float maxAngular = invInertia * MASS_SPEED_SCALE * 360;
+
+ m_shadow.maxSpeed = Lerp( flLerpFactor, m_shadow.maxSpeed, maxSpeed );
+ m_shadow.maxAngular = Lerp( flLerpFactor, m_shadow.maxAngular, maxAngular );
+}
+
+
+QAngle CGrabController::TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer )
+{
+ if ( m_bIgnoreRelativePitch )
+ {
+ matrix3x4_t test;
+ QAngle angleTest = pPlayer->EyeAngles();
+ angleTest.x = 0;
+ AngleMatrix( angleTest, test );
+ return TransformAnglesToLocalSpace( anglesIn, test );
+ }
+ return TransformAnglesToLocalSpace( anglesIn, pPlayer->EntityToWorldTransform() );
+}
+
+QAngle CGrabController::TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer )
+{
+ if ( m_bIgnoreRelativePitch )
+ {
+ matrix3x4_t test;
+ QAngle angleTest = pPlayer->EyeAngles();
+ angleTest.x = 0;
+ AngleMatrix( angleTest, test );
+ return TransformAnglesToWorldSpace( anglesIn, test );
+ }
+ return TransformAnglesToWorldSpace( anglesIn, pPlayer->EntityToWorldTransform() );
+}
+
+
+void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, IPhysicsObject *pPhys, bool bIsMegaPhysCannon )
+{
+ // play the impact sound of the object hitting the player
+ // used as feedback to let the player know he picked up the object
+ PhysicsImpactSound( pPlayer, pPhys, CHAN_STATIC, pPhys->GetMaterialIndex(), pPlayer->VPhysicsGetObject()->GetMaterialIndex(), 1.0, 64 );
+ Vector position;
+ QAngle angles;
+ pPhys->GetPosition( &position, &angles );
+ // If it has a preferred orientation, use that instead.
+ Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles );
+
+// ComputeMaxSpeed( pEntity, pPhys );
+
+ // Carried entities can never block LOS
+ m_bCarriedEntityBlocksLOS = pEntity->BlocksLOS();
+ pEntity->SetBlocksLOS( false );
+ m_controller = physenv->CreateMotionController( this );
+ m_controller->AttachObject( pPhys, true );
+ // Don't do this, it's causing trouble with constraint solvers.
+ //m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY );
+
+ pPhys->Wake();
+ PhysSetGameFlags( pPhys, FVPHYSICS_PLAYER_HELD );
+ SetTargetPosition( position, angles );
+ m_attachedEntity = pEntity;
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ m_flLoadWeight = 0;
+ float damping = 10;
+ float flFactor = count / 7.5f;
+ if ( flFactor < 1.0f )
+ {
+ flFactor = 1.0f;
+ }
+ for ( int i = 0; i < count; i++ )
+ {
+ float mass = pList[i]->GetMass();
+ pList[i]->GetDamping( NULL, &m_savedRotDamping[i] );
+ m_flLoadWeight += mass;
+ m_savedMass[i] = mass;
+
+ // reduce the mass to prevent the player from adding crazy amounts of energy to the system
+ pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor );
+ pList[i]->SetDamping( NULL, &damping );
+ }
+
+ // Give extra mass to the phys object we're actually picking up
+ pPhys->SetMass( REDUCED_CARRY_MASS );
+ pPhys->EnableDrag( false );
+
+ m_errorTime = bIsMegaPhysCannon ? -1.5f : -1.0f; // 1 seconds until error starts accumulating
+ m_error = 0;
+ m_contactAmount = 0;
+
+ m_attachedAnglesPlayerSpace = TransformAnglesToPlayerSpace( angles, pPlayer );
+ if ( m_angleAlignment != 0 )
+ {
+ m_attachedAnglesPlayerSpace = AlignAngles( m_attachedAnglesPlayerSpace, m_angleAlignment );
+ }
+
+ VectorITransform( pEntity->WorldSpaceCenter(), pEntity->EntityToWorldTransform(), m_attachedPositionObjectSpace );
+
+ // If it's a prop, see if it has desired carry angles
+ CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pEntity);
+ if ( pProp )
+ {
+ m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles );
+ }
+ else
+ {
+ m_bHasPreferredCarryAngles = false;
+ }
+}
+
+static void ClampPhysicsVelocity( IPhysicsObject *pPhys, float linearLimit, float angularLimit )
+{
+ Vector vel;
+ AngularImpulse angVel;
+ pPhys->GetVelocity( &vel, &angVel );
+ float speed = VectorNormalize(vel) - linearLimit;
+ float angSpeed = VectorNormalize(angVel) - angularLimit;
+ speed = speed < 0 ? 0 : -speed;
+ angSpeed = angSpeed < 0 ? 0 : -angSpeed;
+ vel *= speed;
+ angVel *= angSpeed;
+ pPhys->AddVelocity( &vel, &angVel );
+}
+
+void CGrabController::DetachEntity()
+{
+ CBaseEntity *pEntity = GetAttached();
+ if ( pEntity )
+ {
+ // Restore the LS blocking state
+ pEntity->SetBlocksLOS( m_bCarriedEntityBlocksLOS );
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ for ( int i = 0; i < count; i++ )
+ {
+ IPhysicsObject *pPhys = pList[i];
+ if ( !pPhys )
+ continue;
+
+ // on the odd chance that it's gone to sleep while under anti-gravity
+ pPhys->EnableDrag( true );
+ pPhys->Wake();
+ pPhys->SetMass( m_savedMass[i] );
+ pPhys->SetDamping( NULL, &m_savedRotDamping[i] );
+ PhysClearGameFlags( pPhys, FVPHYSICS_PLAYER_HELD );
+ if ( pPhys->GetContactPoint( NULL, NULL ) )
+ {
+ PhysForceClearVelocity( pPhys );
+ }
+ else
+ {
+ ClampPhysicsVelocity( pPhys, hl2_normspeed.GetFloat() * 1.5f, 2.0f * 360.0f );
+ }
+
+ }
+ }
+
+ m_attachedEntity = NULL;
+ physenv->DestroyMotionController( m_controller );
+ m_controller = NULL;
+}
+
+static bool InContactWithHeavyObject( IPhysicsObject *pObject, float heavyMass )
+{
+ bool contact = false;
+ IPhysicsFrictionSnapshot *pSnapshot = pObject->CreateFrictionSnapshot();
+ while ( pSnapshot->IsValid() )
+ {
+ IPhysicsObject *pOther = pSnapshot->GetObject( 1 );
+ if ( !pOther->IsMoveable() || pOther->GetMass() > heavyMass )
+ {
+ contact = true;
+ break;
+ }
+ pSnapshot->NextFrictionData();
+ }
+ pObject->DestroyFrictionSnapshot( pSnapshot );
+ return contact;
+}
+
+IMotionEvent::simresult_e CGrabController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
+{
+ game_shadowcontrol_params_t shadowParams = m_shadow;
+ if ( InContactWithHeavyObject( pObject, GetLoadWeight() ) )
+ {
+ m_contactAmount = Approach( 0.1f, m_contactAmount, deltaTime*2.0f );
+ }
+ else
+ {
+ m_contactAmount = Approach( 1.0f, m_contactAmount, deltaTime*2.0f );
+ }
+ shadowParams.maxAngular = m_shadow.maxAngular * m_contactAmount * m_contactAmount * m_contactAmount;
+ m_timeToArrive = pObject->ComputeShadowControl( shadowParams, m_timeToArrive, deltaTime );
+
+ // Slide along the current contact points to fix bouncing problems
+ Vector velocity;
+ AngularImpulse angVel;
+ pObject->GetVelocity( &velocity, &angVel );
+ PhysComputeSlideDirection( pObject, velocity, angVel, &velocity, &angVel, GetLoadWeight() );
+ pObject->SetVelocityInstantaneous( &velocity, NULL );
+
+ linear.Init();
+ angular.Init();
+ m_errorTime += deltaTime;
+
+ return SIM_LOCAL_ACCELERATION;
+}
+
+float CGrabController::GetSavedMass( IPhysicsObject *pObject )
+{
+ CBaseEntity *pHeld = m_attachedEntity;
+ if ( pHeld )
+ {
+ if ( pObject->GetGameData() == (void*)pHeld )
+ {
+ IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
+ int count = pHeld->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
+ for ( int i = 0; i < count; i++ )
+ {
+ if ( pList[i] == pObject )
+ return m_savedMass[i];
+ }
+ }
+ }
+ return 0.0f;
+}
+
+bool CGrabController::UpdateObject( CBasePlayer *pPlayer, float flError )
+{
+ CBaseEntity *pEntity = GetAttached();
+ if ( !pEntity || ComputeError() > flError || pPlayer->GetGroundEntity() == pEntity || !pEntity->VPhysicsGetObject() )
+ {
+ return false;
+ }
+
+ //Adrian: Oops, our object became motion disabled, let go!
+ IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
+ if ( pPhys && pPhys->IsMoveable() == false )
+ {
+ return false;
+ }
+
+ Vector forward, right, up;
+ QAngle playerAngles = pPlayer->EyeAngles();
+ float pitch = AngleDistance(playerAngles.x,0);
+ playerAngles.x = clamp( pitch, -75, 75 );
+ AngleVectors( playerAngles, &forward, &right, &up );
+
+ // Now clamp a sphere of object radius at end to the player's bbox
+ Vector radial = physcollision->CollideGetExtent( pPhys->GetCollide(), vec3_origin, pEntity->GetAbsAngles(), -forward );
+ Vector player2d = pPlayer->CollisionProp()->OBBMaxs();
+ float playerRadius = player2d.Length2D();
+ float radius = playerRadius + fabs(DotProduct( forward, radial ));
+
+ float distance = 24 + ( radius * 2.0f );
+
+ Vector start = pPlayer->Weapon_ShootPosition();
+ Vector end = start + ( forward * distance );
+
+ trace_t tr;
+ CTraceFilterSkipTwoEntities traceFilter( pPlayer, pEntity, COLLISION_GROUP_NONE );
+ Ray_t ray;
+ ray.Init( start, end );
+ enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilter, &tr );
+
+ if ( tr.fraction < 0.5 )
+ {
+ end = start + forward * (radius*0.5f);
+ }
+ else if ( tr.fraction <= 1.0f )
+ {
+ end = start + forward * ( distance - radius );
+ }
+ Vector playerMins, playerMaxs, nearest;
+ pPlayer->CollisionProp()->WorldSpaceAABB( &playerMins, &playerMaxs );
+ Vector playerLine = pPlayer->CollisionProp()->WorldSpaceCenter();
+ CalcClosestPointOnLine( end, playerLine+Vector(0,0,playerMins.z), playerLine+Vector(0,0,playerMaxs.z), nearest, NULL );
+
+ Vector delta = end - nearest;
+ float len = VectorNormalize(delta);
+ if ( len < radius )
+ {
+ end = nearest + radius * delta;
+ }
+/*
+ //Show overlays of radius
+ if ( g_debug_physcannon.GetBool() )
+ {
+ NDebugOverlay::Box( end, -Vector( 2,2,2 ), Vector(2,2,2), 0, 255, 0, true, 0 );
+
+ NDebugOverlay::Box( GetAttached()->WorldSpaceCenter(),
+ -Vector( radius, radius, radius),
+ Vector( radius, radius, radius ),
+ 255, 0, 0,
+ true,
+ 0.0f );
+ }
+*/
+
+ QAngle angles = TransformAnglesFromPlayerSpace( m_attachedAnglesPlayerSpace, pPlayer );
+
+ // If it has a preferred orientation, update to ensure we're still oriented correctly.
+ Pickup_GetPreferredCarryAngles( pEntity, pPlayer, pPlayer->EntityToWorldTransform(), angles );
+
+ // We may be holding a prop that has preferred carry angles
+ if ( m_bHasPreferredCarryAngles )
+ {
+ matrix3x4_t tmp;
+ ComputePlayerMatrix( pPlayer, tmp );
+ angles = TransformAnglesToWorldSpace( m_vecPreferredCarryAngles, tmp );
+ }
+
+ matrix3x4_t attachedToWorld;
+ Vector offset;
+ AngleMatrix( angles, attachedToWorld );
+ VectorRotate( m_attachedPositionObjectSpace, attachedToWorld, offset );
+
+ SetTargetPosition( end - offset, angles );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Player pickup controller
+//-----------------------------------------------------------------------------
+
+class CPlayerPickupController : public CBaseEntity
+{
+ DECLARE_DATADESC();
+ DECLARE_CLASS( CPlayerPickupController, CBaseEntity );
+public:
+ void Init( CBasePlayer *pPlayer, CBaseEntity *pObject );
+ void Shutdown( bool bThrown = false );
+ bool OnControls( CBaseEntity *pControls ) { return true; }
+ void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
+ void OnRestore()
+ {
+ m_grabController.OnRestore();
+ }
+ void VPhysicsUpdate( IPhysicsObject *pPhysics ){}
+ void VPhysicsShadowUpdate( IPhysicsObject *pPhysics ) {}
+
+ bool IsHoldingEntity( CBaseEntity *pEnt );
+ CGrabController &GetGrabController() { return m_grabController; }
+
+private:
+ CGrabController m_grabController;
+ CBasePlayer *m_pPlayer;
+};
+
+LINK_ENTITY_TO_CLASS( player_pickup, CPlayerPickupController );
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CPlayerPickupController )
+
+ DEFINE_EMBEDDED( m_grabController ),
+
+ // Physptrs can't be inside embedded classes
+ DEFINE_PHYSPTR( m_grabController.m_controller ),
+
+ DEFINE_FIELD( m_pPlayer, FIELD_CLASSPTR ),
+
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pPlayer -
+// *pObject -
+//-----------------------------------------------------------------------------
+void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject )
+{
+ // Holster player's weapon
+ if ( pPlayer->GetActiveWeapon() )
+ {
+ if ( !pPlayer->GetActiveWeapon()->Holster() )
+ {
+ Shutdown();
+ return;
+ }
+ }
+
+ // If the target is debris, convert it to non-debris
+ if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
+ {
+ // Interactive debris converts back to debris when it comes to rest
+ pObject->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
+ }
+
+ // done so I'll go across level transitions with the player
+ SetParent( pPlayer );
+ m_grabController.SetIgnorePitch( true );
+ m_grabController.SetAngleAlignment( DOT_30DEGREE );
+ m_pPlayer = pPlayer;
+ IPhysicsObject *pPhysics = pObject->VPhysicsGetObject();
+ Pickup_OnPhysGunPickup( pObject, m_pPlayer );
+ m_grabController.AttachEntity( pPlayer, pObject, pPhysics );
+
+ m_pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION;
+ m_pPlayer->SetUseEntity( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : bool -
+//-----------------------------------------------------------------------------
+void CPlayerPickupController::Shutdown( bool bThrown )
+{
+ CBaseEntity *pObject = m_grabController.GetAttached();
+
+ m_grabController.DetachEntity();
+
+ if ( pObject != NULL )
+ {
+ Pickup_OnPhysGunDrop( pObject, m_pPlayer, bThrown ? THROWN_BY_PLAYER : DROPPED_BY_PLAYER );
+ }
+
+ if ( m_pPlayer )
+ {
+ m_pPlayer->SetUseEntity( NULL );
+ if ( m_pPlayer->GetActiveWeapon() )
+ {
+ m_pPlayer->GetActiveWeapon()->Deploy();
+ }
+
+ m_pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION;
+ }
+ Remove();
+}
+
+
+void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( ToBasePlayer(pActivator) == m_pPlayer )
+ {
+ CBaseEntity *pAttached = m_grabController.GetAttached();
+
+ // UNDONE: Use vphysics stress to decide to drop objects
+ // UNDONE: Must fix case of forcing objects into the ground you're standing on (causes stress) before that will work
+ if ( !pAttached || useType == USE_OFF || (m_pPlayer->m_nButtons & IN_ATTACK2) || m_grabController.ComputeError() > 12 )
+ {
+ Shutdown();
+ return;
+ }
+
+ //Adrian: Oops, our object became motion disabled, let go!
+ IPhysicsObject *pPhys = pAttached->VPhysicsGetObject();
+ if ( pPhys && pPhys->IsMoveable() == false )
+ {
+ Shutdown();
+ return;
+ }
+
+#if STRESS_TEST
+ vphysics_objectstress_t stress;
+ CalculateObjectStress( pAttached->VPhysicsGetObject(), pAttached, &stress );
+ if ( stress.exertedStress > 250 )
+ {
+ Shutdown();
+ return;
+ }
+#endif
+ // +ATTACK will throw phys objects
+ if ( m_pPlayer->m_nButtons & IN_ATTACK )
+ {
+ Shutdown( true );
+ Vector vecLaunch;
+ m_pPlayer->EyeVectors( &vecLaunch );
+ // JAY: Scale this with mass because some small objects really go flying
+ float massFactor = clamp( pAttached->VPhysicsGetObject()->GetMass(), 0.5, 15 );
+ massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 );
+ vecLaunch *= player_throwforce.GetFloat() * massFactor;
+
+ pAttached->VPhysicsGetObject()->ApplyForceCenter( vecLaunch );
+ AngularImpulse aVel = RandomAngularImpulse( -10, 10 ) * massFactor;
+ pAttached->VPhysicsGetObject()->ApplyTorqueCenter( aVel );
+ return;
+ }
+
+ if ( useType == USE_SET )
+ {
+ // update position
+ m_grabController.UpdateObject( m_pPlayer, 12 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEnt -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CPlayerPickupController::IsHoldingEntity( CBaseEntity *pEnt )
+{
+ return ( m_grabController.GetAttached() == pEnt );
+}
+
+ConVar hl1_new_pull( "hl1_new_pull", "1" );
+void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject )
+{
+ if( hl1_new_pull.GetBool() )
+ {
+ CHL1_Player *pHL1Player = dynamic_cast<CHL1_Player*>(pPlayer);
+ if( pHL1Player && !pHL1Player->IsPullingObject() )
+ {
+ pHL1Player->StartPullingObject(pObject);
+ }
+ }
+}
diff --git a/game/server/hl1/hl1_player.h b/game/server/hl1/hl1_player.h
new file mode 100644
index 0000000..03ddf9d
--- /dev/null
+++ b/game/server/hl1/hl1_player.h
@@ -0,0 +1,167 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Player for HL1.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#ifndef HL1_PLAYER_H
+#define HL1_PLAYER_H
+#pragma once
+
+
+#include "player.h"
+
+extern int TrainSpeed(int iSpeed, int iMax);
+extern void CopyToBodyQue( CBaseAnimating *pCorpse );
+
+enum HL1PlayerPhysFlag_e
+{
+ // 1 -- 5 are used by enum PlayerPhysFlag_e in player.h
+
+ PFLAG_ONBARNACLE = ( 1<<6 ) // player is hangning from the barnalce
+};
+
+class IPhysicsPlayerController;
+
+
+//=============================================================================
+//=============================================================================
+class CSuitPowerDevice
+{
+public:
+ CSuitPowerDevice( int bitsID, float flDrainRate ) { m_bitsDeviceID = bitsID; m_flDrainRate = flDrainRate; }
+private:
+ int m_bitsDeviceID; // tells what the device is. DEVICE_SPRINT, DEVICE_FLASHLIGHT, etc. BITMASK!!!!!
+ float m_flDrainRate; // how quickly does this device deplete suit power? ( percent per second )
+
+public:
+ int GetDeviceID( void ) const { return m_bitsDeviceID; }
+ float GetDeviceDrainRate( void ) const { return m_flDrainRate; }
+};
+
+//=============================================================================
+// >> HL1_PLAYER
+//=============================================================================
+class CHL1_Player : public CBasePlayer
+{
+ DECLARE_CLASS( CHL1_Player, CBasePlayer );
+ DECLARE_SERVERCLASS();
+public:
+
+ DECLARE_DATADESC();
+
+ CHL1_Player();
+ ~CHL1_Player( void );
+
+ static CHL1_Player *CreatePlayer( const char *className, edict_t *ed )
+ {
+ CHL1_Player::s_PlayerEdict = ed;
+ return (CHL1_Player*)CreateEntityByName( className );
+ }
+
+ void CreateCorpse( void ) { CopyToBodyQue( this ); };
+
+ void Precache( void );
+ void Spawn(void);
+ void Event_Killed( const CTakeDamageInfo &info );
+ void CheatImpulseCommands( int iImpulse );
+ void PlayerRunCommand( CUserCmd *ucmd, IMoveHelper *moveHelper );
+ void UpdateClientData( void );
+ void OnSave( IEntitySaveUtils *pUtils );
+
+ void CheckTimeBasedDamage( void );
+
+ // from cbasecombatcharacter
+ void InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity );
+
+ Class_T Classify ( void );
+ Class_T m_nControlClass; // Class when player is controlling another entity
+
+ // from CBasePlayer
+ void SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize );
+
+ // Aiming heuristics accessors
+ float GetIdleTime( void ) const { return ( m_flIdleTime - m_flMoveTime ); }
+ float GetMoveTime( void ) const { return ( m_flMoveTime - m_flIdleTime ); }
+ float GetLastDamageTime( void ) const { return m_flLastDamageTime; }
+ bool IsDucking( void ) const { return !!( GetFlags() & FL_DUCKING ); }
+
+ int OnTakeDamage( const CTakeDamageInfo &info );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ void FindMissTargets( void );
+ bool GetMissPosition( Vector *position );
+
+ void OnDamagedByExplosion( const CTakeDamageInfo &info ) { };
+ void PlayerPickupObject( CBasePlayer *pPlayer, CBaseEntity *pObject );
+
+ virtual void CreateViewModel( int index /*=0*/ );
+
+ virtual CBaseEntity *GiveNamedItem( const char *pszName, int iSubType = 0 );
+
+ virtual void OnRestore( void );
+
+ bool IsPullingObject() { return m_bIsPullingObject; }
+ void StartPullingObject( CBaseEntity *pObject );
+ void StopPullingObject();
+ void UpdatePullingObject();
+
+
+protected:
+ void PreThink( void );
+ bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt);
+
+private:
+ Vector m_vecMissPositions[16];
+ int m_nNumMissPositions;
+
+ // Aiming heuristics code
+ float m_flIdleTime; //Amount of time we've been motionless
+ float m_flMoveTime; //Amount of time we've been in motion
+ float m_flLastDamageTime; //Last time we took damage
+ float m_flTargetFindTime;
+
+ EHANDLE m_hPullObject;
+ IPhysicsConstraint *m_pPullConstraint;
+
+
+public:
+
+ // Flashlight Device
+ int FlashlightIsOn( void );
+ void FlashlightTurnOn( void );
+ void FlashlightTurnOff( void );
+ float m_flFlashLightTime; // Time until next battery draw/Recharge
+ CNetworkVar( int, m_nFlashBattery ); // Flashlight Battery Draw
+
+ // For gauss weapon
+// float m_flStartCharge;
+// float m_flAmmoStartCharge;
+// float m_flPlayAftershock;
+// float m_flNextAmmoBurn; // while charging, when to absorb another unit of player's ammo?
+
+ CNetworkVar( float, m_flStartCharge );
+ CNetworkVar( float, m_flAmmoStartCharge );
+ CNetworkVar( float, m_flPlayAftershock );
+ CNetworkVar( float, m_flNextAmmoBurn ); // while charging, when to absorb another unit of player's ammo?
+
+ CNetworkVar( bool, m_bHasLongJump );
+ CNetworkVar( bool, m_bIsPullingObject );
+};
+
+
+//-----------------------------------------------------------------------------
+// Converts an entity to a HL1 player
+//-----------------------------------------------------------------------------
+inline CHL1_Player *ToHL1Player( CBaseEntity *pEntity )
+{
+ if ( !pEntity || !pEntity->IsPlayer() )
+ return NULL;
+#if _DEBUG
+ return dynamic_cast<CHL1_Player *>( pEntity );
+#else
+ return static_cast<CHL1_Player *>( pEntity );
+#endif
+}
+
+#endif //HL1_PLAYER_H
diff --git a/game/server/hl1/hl1_playermove.cpp b/game/server/hl1/hl1_playermove.cpp
new file mode 100644
index 0000000..5ec5a64
--- /dev/null
+++ b/game/server/hl1/hl1_playermove.cpp
@@ -0,0 +1,76 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "player_command.h"
+#include "igamemovement.h"
+#include "in_buttons.h"
+#include "ipredictionsystem.h"
+#include "hl1_player.h"
+
+
+static CMoveData g_MoveData;
+CMoveData *g_pMoveData = &g_MoveData;
+
+IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL;
+
+
+//-----------------------------------------------------------------------------
+// Sets up the move data for Halflife 1
+//-----------------------------------------------------------------------------
+class CHL1PlayerMove : public CPlayerMove
+{
+DECLARE_CLASS( CHL1PlayerMove, CPlayerMove );
+
+public:
+ virtual void StartCommand( CBasePlayer *player, CUserCmd *cmd );
+ virtual void SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move );
+ virtual void FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move );
+};
+
+// PlayerMove Interface
+static CHL1PlayerMove g_PlayerMove;
+
+//-----------------------------------------------------------------------------
+// Singleton accessor
+//-----------------------------------------------------------------------------
+CPlayerMove *PlayerMove()
+{
+ return &g_PlayerMove;
+}
+
+//-----------------------------------------------------------------------------
+// Main setup, finish
+//-----------------------------------------------------------------------------
+
+void CHL1PlayerMove::StartCommand( CBasePlayer *player, CUserCmd *cmd )
+{
+ BaseClass::StartCommand( player, cmd );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is called pre player movement and copies all the data necessary
+// from the player for movement. (Server-side, the client-side version
+// of this code can be found in prediction.cpp.)
+//-----------------------------------------------------------------------------
+void CHL1PlayerMove::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
+{
+ BaseClass::SetupMove( player, ucmd, pHelper, move );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: This is called post player movement to copy back all data that
+// movement could have modified and that is necessary for future
+// movement. (Server-side, the client-side version of this code can
+// be found in prediction.cpp.)
+//-----------------------------------------------------------------------------
+void CHL1PlayerMove::FinishMove( CBasePlayer *player, CUserCmd *ucmd, CMoveData *move )
+{
+ // Call the default FinishMove code.
+ BaseClass::FinishMove( player, ucmd, move );
+}
diff --git a/game/server/hl1/hl1_weapon_crowbar.cpp b/game/server/hl1/hl1_weapon_crowbar.cpp
new file mode 100644
index 0000000..f11f640
--- /dev/null
+++ b/game/server/hl1/hl1_weapon_crowbar.cpp
@@ -0,0 +1,379 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Crowbar - an old favorite
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "hl1mp_basecombatweapon_shared.h"
+
+#ifdef CLIENT_DLL
+#include "c_baseplayer.h"
+#include "fx_impact.h"
+#include "fx.h"
+#else
+#include "player.h"
+#include "soundent.h"
+#endif
+
+#include "gamerules.h"
+#include "ammodef.h"
+#include "mathlib/mathlib.h"
+#include "in_buttons.h"
+
+#include "vstdlib/random.h"
+
+extern ConVar sk_plr_dmg_crowbar;
+
+#define CROWBAR_RANGE 64.0f
+#define CROWBAR_REFIRE_MISS 0.5f
+#define CROWBAR_REFIRE_HIT 0.25f
+
+
+#ifdef CLIENT_DLL
+#define CWeaponCrowbar C_WeaponCrowbar
+#endif
+
+//-----------------------------------------------------------------------------
+// CWeaponCrowbar
+//-----------------------------------------------------------------------------
+
+class CWeaponCrowbar : public CBaseHL1MPCombatWeapon
+{
+ DECLARE_CLASS( CWeaponCrowbar, CBaseHL1MPCombatWeapon );
+public:
+ DECLARE_NETWORKCLASS();
+ DECLARE_PREDICTABLE();
+#ifndef CLIENT_DLL
+ DECLARE_DATADESC();
+#endif
+
+ CWeaponCrowbar();
+
+ void Precache( void );
+ virtual void ItemPostFrame( void );
+ void PrimaryAttack( void );
+
+public:
+ trace_t m_traceHit;
+ Activity m_nHitActivity;
+
+private:
+ virtual void Swing( void );
+ virtual void Hit( void );
+ virtual void ImpactEffect( void );
+ void ImpactSound( CBaseEntity *pHitEntity );
+ virtual Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner );
+
+public:
+
+};
+
+IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCrowbar, DT_WeaponCrowbar );
+
+BEGIN_NETWORK_TABLE( CWeaponCrowbar, DT_WeaponCrowbar )
+/// what
+END_NETWORK_TABLE()
+
+BEGIN_PREDICTION_DATA( CWeaponCrowbar )
+END_PREDICTION_DATA()
+
+LINK_ENTITY_TO_CLASS( weapon_crowbar, CWeaponCrowbar );
+PRECACHE_WEAPON_REGISTER( weapon_crowbar );
+
+#ifndef CLIENT_DLL
+BEGIN_DATADESC( CWeaponCrowbar )
+
+ // DEFINE_FIELD( m_trLineHit, trace_t ),
+ // DEFINE_FIELD( m_trHullHit, trace_t ),
+ // DEFINE_FIELD( m_nHitActivity, FIELD_INTEGER ),
+ // DEFINE_FIELD( m_traceHit, trace_t ),
+
+ // Class CWeaponCrowbar:
+ // DEFINE_FIELD( m_nHitActivity, FIELD_INTEGER ),
+
+ // Function Pointers
+ DEFINE_FUNCTION( Hit ),
+
+END_DATADESC()
+#endif
+
+#define BLUDGEON_HULL_DIM 16
+
+static const Vector g_bludgeonMins(-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM);
+static const Vector g_bludgeonMaxs(BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM);
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CWeaponCrowbar::CWeaponCrowbar()
+{
+ m_bFiresUnderwater = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache the weapon
+//-----------------------------------------------------------------------------
+void CWeaponCrowbar::Precache( void )
+{
+ //Call base class first
+ BaseClass::Precache();
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Update weapon
+//------------------------------------------------------------------------------
+void CWeaponCrowbar::ItemPostFrame( void )
+{
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+
+ if ( pOwner == NULL )
+ return;
+
+ if ( (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime) )
+ {
+ PrimaryAttack();
+ }
+ else
+ {
+ WeaponIdle();
+ return;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CWeaponCrowbar::PrimaryAttack()
+{
+ Swing();
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose: Implement impact function
+//------------------------------------------------------------------------------
+void CWeaponCrowbar::Hit( void )
+{
+ //Make sound for the AI
+#ifndef CLIENT_DLL
+
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+
+ CSoundEnt::InsertSound( SOUND_BULLET_IMPACT, m_traceHit.endpos, 400, 0.2f, pPlayer );
+
+ CBaseEntity *pHitEntity = m_traceHit.m_pEnt;
+
+ //Apply damage to a hit target
+ if ( pHitEntity != NULL )
+ {
+ Vector hitDirection;
+ pPlayer->EyeVectors( &hitDirection, NULL, NULL );
+ VectorNormalize( hitDirection );
+
+ ClearMultiDamage();
+ CTakeDamageInfo info( GetOwner(), GetOwner(), sk_plr_dmg_crowbar.GetFloat(), DMG_CLUB );
+ CalculateMeleeDamageForce( &info, hitDirection, m_traceHit.endpos );
+ pHitEntity->DispatchTraceAttack( info, hitDirection, &m_traceHit );
+ ApplyMultiDamage();
+
+ // Now hit all triggers along the ray that...
+ TraceAttackToTriggers( CTakeDamageInfo( GetOwner(), GetOwner(), sk_plr_dmg_crowbar.GetFloat(), DMG_CLUB ), m_traceHit.startpos, m_traceHit.endpos, hitDirection );
+
+ //Play an impact sound
+ ImpactSound( pHitEntity );
+ }
+#endif
+
+ //Apply an impact effect
+ ImpactEffect();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play the impact sound
+// Input : pHitEntity - entity that we hit
+// assumes pHitEntity is not null
+//-----------------------------------------------------------------------------
+void CWeaponCrowbar::ImpactSound( CBaseEntity *pHitEntity )
+{
+ bool bIsWorld = ( pHitEntity->entindex() == 0 );
+#ifndef CLIENT_DLL
+ if ( !bIsWorld )
+ {
+ bIsWorld |= pHitEntity->Classify() == CLASS_NONE || pHitEntity->Classify() == CLASS_MACHINE;
+ }
+#endif
+
+ if( bIsWorld )
+ {
+ WeaponSound( MELEE_HIT_WORLD );
+ }
+ else
+ {
+ WeaponSound( MELEE_HIT );
+ }
+}
+
+Activity CWeaponCrowbar::ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner )
+{
+ int i, j, k;
+ float distance;
+ const float *minmaxs[2] = {mins.Base(), maxs.Base()};
+ trace_t tmpTrace;
+ Vector vecHullEnd = hitTrace.endpos;
+ Vector vecEnd;
+
+ distance = 1e6f;
+ Vector vecSrc = hitTrace.startpos;
+
+ vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2);
+ UTIL_TraceLine( vecSrc, vecHullEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &tmpTrace );
+ if ( tmpTrace.fraction == 1.0 )
+ {
+ for ( i = 0; i < 2; i++ )
+ {
+ for ( j = 0; j < 2; j++ )
+ {
+ for ( k = 0; k < 2; k++ )
+ {
+ vecEnd.x = vecHullEnd.x + minmaxs[i][0];
+ vecEnd.y = vecHullEnd.y + minmaxs[j][1];
+ vecEnd.z = vecHullEnd.z + minmaxs[k][2];
+
+ UTIL_TraceLine( vecSrc, vecEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &tmpTrace );
+ if ( tmpTrace.fraction < 1.0 )
+ {
+ float thisDistance = (tmpTrace.endpos - vecSrc).Length();
+ if ( thisDistance < distance )
+ {
+ hitTrace = tmpTrace;
+ distance = thisDistance;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ hitTrace = tmpTrace;
+ }
+
+
+ return ACT_VM_HITCENTER;
+}
+
+#ifdef HL1MP_CLIENT_DLL
+//-----------------------------------------------------------------------------
+// Purpose: Handle jeep impacts
+//-----------------------------------------------------------------------------
+void ImpactCrowbarCallback( const CEffectData &data )
+{
+ trace_t tr;
+ Vector vecOrigin, vecStart, vecShotDir;
+ int iMaterial, iDamageType, iHitbox;
+ short nSurfaceProp;
+ C_BaseEntity *pEntity = ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, nSurfaceProp, iMaterial, iDamageType, iHitbox );
+
+ bool bIsWorld = ( pEntity->entindex() == 0 );
+
+ if ( !pEntity )
+ {
+ // This happens for impacts that occur on an object that's then destroyed.
+ // Clear out the fraction so it uses the server's data
+ tr.fraction = 1.0;
+ GetActiveWeapon()->WeaponSound( bIsWorld ? MELEE_HIT_WORLD : MELEE_HIT );
+ return;
+ }
+
+ // If we hit, perform our custom effects and play the sound
+ if ( Impact( vecOrigin, vecStart, iMaterial, iDamageType, iHitbox, pEntity, tr ) )
+ {
+ // Check for custom effects based on the Decal index
+ PerformCustomEffects( vecOrigin, tr, vecShotDir, iMaterial, 2 );
+ }
+
+ GetActiveWeapon()->WeaponSound( bIsWorld ? MELEE_HIT_WORLD : MELEE_HIT );
+}
+
+DECLARE_CLIENT_EFFECT( "ImpactCrowbar", ImpactCrowbarCallback );
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponCrowbar::ImpactEffect( void )
+{
+ //FIXME: need new decals
+#ifdef HL1MP_CLIENT_DLL
+ // in hl1mp force the basic crowbar sound
+ UTIL_ImpactTrace( &m_traceHit, DMG_CLUB, "ImpactCrowbar" );
+#else
+ UTIL_ImpactTrace( &m_traceHit, DMG_CLUB );
+#endif
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Starts the swing of the weapon and determines the animation
+//------------------------------------------------------------------------------
+void CWeaponCrowbar::Swing( void )
+{
+ // Try a ray
+ CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
+ if ( !pOwner )
+ return;
+
+ Vector swingStart = pOwner->Weapon_ShootPosition( );
+ Vector forward;
+
+ pOwner->EyeVectors( &forward, NULL, NULL );
+
+ Vector swingEnd = swingStart + forward * CROWBAR_RANGE;
+
+ UTIL_TraceLine( swingStart, swingEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &m_traceHit );
+ m_nHitActivity = ACT_VM_HITCENTER;
+
+ if ( m_traceHit.fraction == 1.0 )
+ {
+ float bludgeonHullRadius = 1.732f * BLUDGEON_HULL_DIM; // hull is +/- 16, so use cuberoot of 2 to determine how big the hull is from center to the corner point
+
+ // Back off by hull "radius"
+ swingEnd -= forward * bludgeonHullRadius;
+
+ UTIL_TraceHull( swingStart, swingEnd, g_bludgeonMins, g_bludgeonMaxs, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &m_traceHit );
+ if ( m_traceHit.fraction < 1.0 )
+ {
+ m_nHitActivity = ChooseIntersectionPointAndActivity( m_traceHit, g_bludgeonMins, g_bludgeonMaxs, pOwner );
+ }
+ }
+
+
+ // -------------------------
+ // Miss
+ // -------------------------
+ if ( m_traceHit.fraction == 1.0f )
+ {
+ m_nHitActivity = ACT_VM_MISSCENTER;
+
+ //Play swing sound
+ WeaponSound( SINGLE );
+
+ //Setup our next attack times
+ m_flNextPrimaryAttack = gpGlobals->curtime + CROWBAR_REFIRE_MISS;
+ }
+ else
+ {
+ Hit();
+
+ //Setup our next attack times
+ m_flNextPrimaryAttack = gpGlobals->curtime + CROWBAR_REFIRE_HIT;
+ }
+
+ //Send the anim
+ SendWeaponAnim( m_nHitActivity );
+ pOwner->SetAnimation( PLAYER_ATTACK1 );
+}
diff --git a/game/server/hl1/hl1_weapon_snark.cpp b/game/server/hl1/hl1_weapon_snark.cpp
new file mode 100644
index 0000000..9339297
--- /dev/null
+++ b/game/server/hl1/hl1_weapon_snark.cpp
@@ -0,0 +1,208 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Snark
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "npcevent.h"
+#include "hl1_basecombatweapon_shared.h"
+#include "basecombatcharacter.h"
+#include "ai_basenpc.h"
+#include "player.h"
+#include "gamerules.h"
+#include "in_buttons.h"
+#include "soundent.h"
+#include "game.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "hl1_npc_snark.h"
+#include "beam_shared.h"
+
+
+//-----------------------------------------------------------------------------
+// CWeaponSnark
+//-----------------------------------------------------------------------------
+
+
+#define SNARK_NEST_MODEL "models/w_sqknest.mdl"
+
+
+class CWeaponSnark : public CBaseHL1CombatWeapon
+{
+ DECLARE_CLASS( CWeaponSnark, CBaseHL1CombatWeapon );
+public:
+
+ CWeaponSnark( void );
+
+ void Precache( void );
+ void PrimaryAttack( void );
+ void WeaponIdle( void );
+ bool Deploy( void );
+ bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+private:
+ bool m_bJustThrown;
+};
+
+LINK_ENTITY_TO_CLASS( weapon_snark, CWeaponSnark );
+
+PRECACHE_WEAPON_REGISTER( weapon_snark );
+
+IMPLEMENT_SERVERCLASS_ST( CWeaponSnark, DT_WeaponSnark )
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CWeaponSnark )
+ DEFINE_FIELD( m_bJustThrown, FIELD_BOOLEAN ),
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CWeaponSnark::CWeaponSnark( void )
+{
+ m_bReloadsSingly = false;
+ m_bFiresUnderwater = true;
+ m_bJustThrown = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponSnark::Precache( void )
+{
+ BaseClass::Precache();
+
+ PrecacheScriptSound( "WpnSnark.PrimaryAttack" );
+ PrecacheScriptSound( "WpnSnark.Deploy" );
+
+ UTIL_PrecacheOther("monster_snark");
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponSnark::PrimaryAttack( void )
+{
+ // Only the player fires this way so we can cast
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+
+ if ( !pPlayer )
+ {
+ return;
+ }
+
+ if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
+ return;
+
+ Vector vecForward;
+ pPlayer->EyeVectors( &vecForward );
+
+ // find place to toss monster
+ // Does this need to consider a crouched player?
+ Vector vecStart = pPlayer->WorldSpaceCenter() + (vecForward * 20);
+ Vector vecEnd = vecStart + (vecForward * 44);
+ trace_t tr;
+ UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+ if ( tr.allsolid || tr.startsolid || tr.fraction <= 0.25 )
+ return;
+
+ // player "shoot" animation
+ SendWeaponAnim( ACT_VM_PRIMARYATTACK );
+ pPlayer->SetAnimation( PLAYER_ATTACK1 );
+
+ CSnark *pSnark = (CSnark*)Create( "monster_snark", tr.endpos, pPlayer->EyeAngles(), GetOwner() );
+ if ( pSnark )
+ {
+ pSnark->SetAbsVelocity( vecForward * 200 + pPlayer->GetAbsVelocity() );
+ }
+
+ // play hunt sound
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "WpnSnark.PrimaryAttack" );
+
+ CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 200, 0.2 );
+
+ pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType );
+
+ m_bJustThrown = true;
+
+ m_flNextPrimaryAttack = gpGlobals->curtime + 0.3;
+ SetWeaponIdleTime( gpGlobals->curtime + 1.0 );
+}
+
+void CWeaponSnark::WeaponIdle( void )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+
+ if ( !pPlayer )
+ {
+ return;
+ }
+
+ if ( !HasWeaponIdleTimeElapsed() )
+ return;
+
+ if ( m_bJustThrown )
+ {
+ m_bJustThrown = false;
+
+ if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
+ {
+ if ( !pPlayer->SwitchToNextBestWeapon( pPlayer->GetActiveWeapon() ) )
+ Holster();
+ }
+ else
+ {
+ SendWeaponAnim( ACT_VM_DRAW );
+ SetWeaponIdleTime( gpGlobals->curtime + random->RandomFloat( 10, 15 ) );
+ }
+ }
+ else
+ {
+ if ( random->RandomFloat( 0, 1 ) <= 0.75 )
+ {
+ SendWeaponAnim( ACT_VM_IDLE );
+ }
+ else
+ {
+ SendWeaponAnim( ACT_VM_FIDGET );
+ }
+ }
+}
+
+bool CWeaponSnark::Deploy( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "WpnSnark.Deploy" );
+
+ return BaseClass::Deploy();
+}
+
+bool CWeaponSnark::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+ if ( !pPlayer )
+ {
+ return false;
+ }
+
+ if ( !BaseClass::Holster( pSwitchingTo ) )
+ {
+ return false;
+ }
+
+ if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
+ {
+ SetThink( &CWeaponSnark::DestroyItem );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+
+ pPlayer->SetNextAttack( gpGlobals->curtime + 0.5 );
+
+ return true;
+}
diff --git a/game/server/hl1/hl1_weapon_tripmine.cpp b/game/server/hl1/hl1_weapon_tripmine.cpp
new file mode 100644
index 0000000..8818cf7
--- /dev/null
+++ b/game/server/hl1/hl1_weapon_tripmine.cpp
@@ -0,0 +1,573 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Tripmine
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "npcevent.h"
+#include "hl1_basecombatweapon_shared.h"
+#include "basecombatcharacter.h"
+#include "ai_basenpc.h"
+#include "player.h"
+#include "gamerules.h"
+#include "in_buttons.h"
+#include "soundent.h"
+#include "game.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "hl1_player.h"
+#include "hl1_basegrenade.h"
+#include "beam_shared.h"
+
+extern ConVar sk_plr_dmg_tripmine;
+
+
+//-----------------------------------------------------------------------------
+// CWeaponTripMine
+//-----------------------------------------------------------------------------
+
+
+#define TRIPMINE_MODEL "models/w_tripmine.mdl"
+
+
+class CWeaponTripMine : public CBaseHL1CombatWeapon
+{
+ DECLARE_CLASS( CWeaponTripMine, CBaseHL1CombatWeapon );
+public:
+
+ CWeaponTripMine( void );
+
+ void Spawn( void );
+ void Precache( void );
+ void Equip( CBaseCombatCharacter *pOwner );
+ void PrimaryAttack( void );
+ void WeaponIdle( void );
+ bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
+
+ DECLARE_SERVERCLASS();
+ DECLARE_DATADESC();
+
+private:
+ int m_iGroundIndex;
+ int m_iPickedUpIndex;
+};
+
+LINK_ENTITY_TO_CLASS( weapon_tripmine, CWeaponTripMine );
+
+PRECACHE_WEAPON_REGISTER( weapon_tripmine );
+
+IMPLEMENT_SERVERCLASS_ST( CWeaponTripMine, DT_WeaponTripMine )
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CWeaponTripMine )
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CWeaponTripMine::CWeaponTripMine( void )
+{
+ m_bReloadsSingly = false;
+ m_bFiresUnderwater = true;
+}
+
+void CWeaponTripMine::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ m_iWorldModelIndex = m_iGroundIndex;
+ SetModel( TRIPMINE_MODEL );
+
+ SetActivity( ACT_TRIPMINE_GROUND );
+ ResetSequenceInfo( );
+ m_flPlaybackRate = 0;
+
+ if ( !g_pGameRules->IsDeathmatch() )
+ {
+ UTIL_SetSize( this, Vector(-16, -16, 0), Vector(16, 16, 28) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponTripMine::Precache( void )
+{
+ BaseClass::Precache();
+
+ m_iGroundIndex = PrecacheModel( TRIPMINE_MODEL );
+ m_iPickedUpIndex = PrecacheModel( GetWorldModel() );
+
+ UTIL_PrecacheOther( "monster_tripmine" );
+}
+
+void CWeaponTripMine::Equip( CBaseCombatCharacter *pOwner )
+{
+ m_iWorldModelIndex = m_iPickedUpIndex;
+
+ BaseClass::Equip( pOwner );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CWeaponTripMine::PrimaryAttack( void )
+{
+ CHL1_Player *pPlayer = ToHL1Player( GetOwner() );
+ if ( !pPlayer )
+ {
+ return;
+ }
+
+ if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
+ return;
+
+ Vector vecAiming = pPlayer->GetAutoaimVector( 0 );
+ Vector vecSrc = pPlayer->Weapon_ShootPosition( );
+
+ trace_t tr;
+
+ UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 64, MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0 )
+ {
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if ( pEntity && !( pEntity->GetFlags() & FL_CONVEYOR ) )
+ {
+ QAngle angles;
+ VectorAngles( tr.plane.normal, angles );
+
+ CBaseEntity::Create( "monster_tripmine", tr.endpos + tr.plane.normal * 2, angles, pPlayer );
+
+ pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType );
+
+ pPlayer->SetAnimation( PLAYER_ATTACK1 );
+
+ if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
+ {
+ if ( !pPlayer->SwitchToNextBestWeapon( pPlayer->GetActiveWeapon() ) )
+ Holster();
+ }
+ else
+ {
+ SendWeaponAnim( ACT_VM_DRAW );
+ SetWeaponIdleTime( gpGlobals->curtime + random->RandomFloat( 10, 15 ) );
+ }
+
+ m_flNextPrimaryAttack = gpGlobals->curtime + 0.5;
+
+ SetWeaponIdleTime( gpGlobals->curtime ); // MO curtime correct ?
+ }
+ }
+ else
+ {
+ SetWeaponIdleTime( m_flTimeWeaponIdle = gpGlobals->curtime + random->RandomFloat( 10, 15 ) );
+ }
+}
+
+void CWeaponTripMine::WeaponIdle( void )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+
+ if ( !pPlayer )
+ {
+ return;
+ }
+
+ if ( !HasWeaponIdleTimeElapsed() )
+ return;
+
+ int iAnim;
+
+ if ( random->RandomFloat( 0, 1 ) <= 0.75 )
+ {
+ iAnim = ACT_VM_IDLE;
+ }
+ else
+ {
+ iAnim = ACT_VM_FIDGET;
+ }
+
+ SendWeaponAnim( iAnim );
+}
+
+bool CWeaponTripMine::Holster( CBaseCombatWeapon *pSwitchingTo )
+{
+ CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
+ if ( !pPlayer )
+ {
+ return false;
+ }
+
+ if ( !BaseClass::Holster( pSwitchingTo ) )
+ {
+ return false;
+ }
+
+ if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
+ {
+ SetThink( &CWeaponTripMine::DestroyItem );
+ SetNextThink( gpGlobals->curtime + 0.1 );
+ }
+
+ pPlayer->SetNextAttack( gpGlobals->curtime + 0.5 );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// CTripmineGrenade
+//-----------------------------------------------------------------------------
+
+#define TRIPMINE_BEAM_SPRITE "sprites/laserbeam.vmt"
+
+class CTripmineGrenade : public CHL1BaseGrenade
+{
+ DECLARE_CLASS( CTripmineGrenade, CHL1BaseGrenade );
+public:
+ CTripmineGrenade();
+ void Spawn( void );
+ void Precache( void );
+
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+
+ void WarningThink( void );
+ void PowerupThink( void );
+ void BeamBreakThink( void );
+ void DelayDeathThink( void );
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ DECLARE_DATADESC();
+
+private:
+ void MakeBeam( void );
+ void KillBeam( void );
+
+private:
+ float m_flPowerUp;
+ Vector m_vecDir;
+ Vector m_vecEnd;
+ float m_flBeamLength;
+
+ CHandle<CBaseEntity> m_hRealOwner;
+ CHandle<CBeam> m_hBeam;
+
+ CHandle<CBaseEntity> m_hStuckOn;
+ Vector m_posStuckOn;
+ QAngle m_angStuckOn;
+
+ int m_iLaserModel;
+};
+
+LINK_ENTITY_TO_CLASS( monster_tripmine, CTripmineGrenade );
+
+BEGIN_DATADESC( CTripmineGrenade )
+ DEFINE_FIELD( m_flPowerUp, FIELD_TIME ),
+ DEFINE_FIELD( m_vecDir, FIELD_VECTOR ),
+ DEFINE_FIELD( m_vecEnd, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flBeamLength, FIELD_FLOAT ),
+// DEFINE_FIELD( m_hBeam, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hRealOwner, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hStuckOn, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_posStuckOn, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_angStuckOn, FIELD_VECTOR ),
+ //DEFINE_FIELD( m_iLaserModel, FIELD_INTEGER ),
+
+ // Function Pointers
+ DEFINE_THINKFUNC( WarningThink ),
+ DEFINE_THINKFUNC( PowerupThink ),
+ DEFINE_THINKFUNC( BeamBreakThink ),
+ DEFINE_THINKFUNC( DelayDeathThink ),
+END_DATADESC()
+
+
+CTripmineGrenade::CTripmineGrenade()
+{
+ m_vecDir.Init();
+ m_vecEnd.Init();
+}
+
+void CTripmineGrenade::Spawn( void )
+{
+ Precache( );
+ // motor
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ SetModel( TRIPMINE_MODEL );
+
+ // Don't collide with the player (the beam will still be tripped by one, however)
+ SetCollisionGroup( COLLISION_GROUP_WEAPON );
+
+ SetCycle( 0 );
+ SetSequence( SelectWeightedSequence( ACT_TRIPMINE_WORLD ) );
+ ResetSequenceInfo();
+ m_flPlaybackRate = 0;
+
+ UTIL_SetSize( this, Vector( -8, -8, -8), Vector(8, 8, 8) );
+
+ m_flDamage = sk_plr_dmg_tripmine.GetFloat();
+ m_DmgRadius = m_flDamage * 2.5;
+
+ if ( m_spawnflags & 1 )
+ {
+ // power up quickly
+ m_flPowerUp = gpGlobals->curtime + 1.0;
+ }
+ else
+ {
+ // power up in 2.5 seconds
+ m_flPowerUp = gpGlobals->curtime + 2.5;
+ }
+
+ SetThink( &CTripmineGrenade::PowerupThink );
+ SetNextThink( gpGlobals->curtime + 0.2 );
+
+ m_takedamage = DAMAGE_YES;
+
+ m_iHealth = 1;
+
+ if ( GetOwnerEntity() != NULL )
+ {
+ // play deploy sound
+ EmitSound( "TripmineGrenade.Deploy" );
+ EmitSound( "TripmineGrenade.Charge" );
+
+ m_hRealOwner = GetOwnerEntity();
+ }
+ AngleVectors( GetAbsAngles(), &m_vecDir );
+ m_vecEnd = GetAbsOrigin() + m_vecDir * MAX_TRACE_LENGTH;
+}
+
+
+void CTripmineGrenade::Precache( void )
+{
+ PrecacheModel( TRIPMINE_MODEL );
+ m_iLaserModel = PrecacheModel( TRIPMINE_BEAM_SPRITE );
+
+ PrecacheScriptSound( "TripmineGrenade.Deploy" );
+ PrecacheScriptSound( "TripmineGrenade.Charge" );
+ PrecacheScriptSound( "TripmineGrenade.Activate" );
+
+}
+
+
+void CTripmineGrenade::WarningThink( void )
+{
+ // set to power up
+ SetThink( &CTripmineGrenade::PowerupThink );
+ SetNextThink( gpGlobals->curtime + 1.0f );
+}
+
+
+void CTripmineGrenade::PowerupThink( void )
+{
+ if ( m_hStuckOn == NULL )
+ {
+ trace_t tr;
+ CBaseEntity *pOldOwner = GetOwnerEntity();
+
+ // don't explode if the player is standing in front of the laser
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + m_vecDir * 32, MASK_SHOT, NULL, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.m_pEnt && pOldOwner &&
+ ( tr.m_pEnt == pOldOwner ) && pOldOwner->IsPlayer() )
+ {
+ m_flPowerUp += 0.1; //delay the arming
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ return;
+ }
+
+ // find out what we've been stuck on
+ SetOwnerEntity( NULL );
+
+ UTIL_TraceLine( GetAbsOrigin() + m_vecDir * 8, GetAbsOrigin() - m_vecDir * 32, MASK_SHOT, pOldOwner, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.startsolid )
+ {
+ SetOwnerEntity( pOldOwner );
+ m_flPowerUp += 0.1;
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ return;
+ }
+ if ( tr.fraction < 1.0 )
+ {
+ SetOwnerEntity( tr.m_pEnt );
+ m_hStuckOn = tr.m_pEnt;
+ m_posStuckOn = m_hStuckOn->GetAbsOrigin();
+ m_angStuckOn = m_hStuckOn->GetAbsAngles();
+ }
+ else
+ {
+ // somehow we've been deployed on nothing, or something that was there, but now isn't.
+ // remove ourselves
+
+ StopSound( "TripmineGrenade.Deploy" );
+ StopSound( "TripmineGrenade.Charge" );
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+// ALERT( at_console, "WARNING:Tripmine at %.0f, %.0f, %.0f removed\n", pev->origin.x, pev->origin.y, pev->origin.z );
+ KillBeam();
+ return;
+ }
+ }
+ else if ( (m_posStuckOn != m_hStuckOn->GetAbsOrigin()) || (m_angStuckOn != m_hStuckOn->GetAbsAngles()) )
+ {
+ // what we were stuck on has moved, or rotated. Create a tripmine weapon and drop to ground
+
+ StopSound( "TripmineGrenade.Deploy" );
+ StopSound( "TripmineGrenade.Charge" );
+ CBaseEntity *pMine = Create( "weapon_tripmine", GetAbsOrigin() + m_vecDir * 24, GetAbsAngles() );
+ pMine->AddSpawnFlags( SF_NORESPAWN );
+
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ KillBeam();
+ return;
+ }
+
+ if ( gpGlobals->curtime > m_flPowerUp )
+ {
+ MakeBeam( );
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ m_bIsLive = true;
+
+ // play enabled sound
+ EmitSound( "TripmineGrenade.Activate" );
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+void CTripmineGrenade::KillBeam( void )
+{
+ if ( m_hBeam )
+ {
+ UTIL_Remove( m_hBeam );
+ m_hBeam = NULL;
+ }
+}
+
+
+void CTripmineGrenade::MakeBeam( void )
+{
+ trace_t tr;
+
+ UTIL_TraceLine( GetAbsOrigin(), m_vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ m_flBeamLength = tr.fraction;
+
+ // set to follow laser spot
+ SetThink( &CTripmineGrenade::BeamBreakThink );
+
+ SetNextThink( gpGlobals->curtime + 1.0f );
+
+ Vector vecTmpEnd = GetAbsOrigin() + m_vecDir * MAX_TRACE_LENGTH * m_flBeamLength;
+
+ m_hBeam = CBeam::BeamCreate( TRIPMINE_BEAM_SPRITE, 1.0 );
+ m_hBeam->PointEntInit( vecTmpEnd, this );
+ m_hBeam->SetColor( 0, 214, 198 );
+ m_hBeam->SetScrollRate( 25.5 );
+ m_hBeam->SetBrightness( 64 );
+ m_hBeam->AddSpawnFlags( SF_BEAM_TEMPORARY ); // so it won't save and come back to haunt us later..
+}
+
+
+void CTripmineGrenade::BeamBreakThink( void )
+{
+ bool bBlowup = false;
+ trace_t tr;
+
+ // NOT MASK_SHOT because we want only simple hit boxes
+ UTIL_TraceLine( GetAbsOrigin(), m_vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
+
+ // ALERT( at_console, "%f : %f\n", tr.flFraction, m_flBeamLength );
+
+ // respawn detect.
+ if ( !m_hBeam )
+ {
+ MakeBeam();
+
+ trace_t stuckOnTrace;
+ Vector forward;
+ GetVectors( &forward, NULL, NULL );
+
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - forward * 12.0f, MASK_SOLID, this, COLLISION_GROUP_NONE, &stuckOnTrace );
+
+ if ( stuckOnTrace.m_pEnt )
+ {
+ m_hStuckOn = stuckOnTrace.m_pEnt; // reset stuck on ent too
+ }
+ }
+
+ CBaseEntity *pEntity = tr.m_pEnt;
+ CBaseCombatCharacter *pBCC = ToBaseCombatCharacter( pEntity );
+
+ if ( pBCC || fabs( m_flBeamLength - tr.fraction ) > 0.001 )
+ {
+ bBlowup = true;
+ }
+ else
+ {
+ if ( m_hStuckOn == NULL )
+ bBlowup = true;
+ else if ( m_posStuckOn != m_hStuckOn->GetAbsOrigin() )
+ bBlowup = true;
+ else if ( m_angStuckOn != m_hStuckOn->GetAbsAngles() )
+ bBlowup = true;
+ }
+
+ if ( bBlowup )
+ {
+ SetOwnerEntity( m_hRealOwner );
+ m_iHealth = 0;
+ Event_Killed( CTakeDamageInfo( this, m_hRealOwner, 100, GIB_NORMAL ) );
+ return;
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+}
+/*
+int CTripmineGrenade::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ if (gpGlobals->curtime < m_flPowerUp && info.GetDamage() < m_iHealth)
+ {
+ // disable
+ // Create( "weapon_tripmine", GetLocalOrigin() + m_vecDir * 24, GetAngles() );
+ SetThink( &CBaseEntity::SUB_Remove );
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ KillBeam();
+ return 0;
+ }
+ return BaseClass::OnTakeDamage_Alive( info );
+}*/
+
+void CTripmineGrenade::Event_Killed( const CTakeDamageInfo &info )
+{
+ m_takedamage = DAMAGE_NO;
+
+ if ( info.GetAttacker() && ( info.GetAttacker()->GetFlags() & FL_CLIENT ) )
+ {
+ // some client has destroyed this mine, he'll get credit for any kills
+ SetOwnerEntity( info.GetAttacker() );
+ }
+
+ SetThink( &CTripmineGrenade::DelayDeathThink );
+ SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.3 ) );
+
+ StopSound( "TripmineGrenade.Charge" );
+}
+
+void CTripmineGrenade::DelayDeathThink( void )
+{
+ KillBeam();
+ trace_t tr;
+ UTIL_TraceLine ( GetAbsOrigin() + m_vecDir * 8, GetAbsOrigin() - m_vecDir * 64, MASK_SOLID, this, COLLISION_GROUP_NONE, & tr);
+
+ Explode( &tr, DMG_BLAST );
+}
diff --git a/game/server/hl1/hl1_weaponbox.cpp b/game/server/hl1/hl1_weaponbox.cpp
new file mode 100644
index 0000000..07a745e
--- /dev/null
+++ b/game/server/hl1/hl1_weaponbox.cpp
@@ -0,0 +1,242 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "hl1_items.h"
+#include "ammodef.h"
+
+
+#define WEAPONBOX_MODEL "models/w_weaponbox.mdl"
+
+
+class CWeaponBox : public CHL1Item
+{
+public:
+ DECLARE_CLASS( CWeaponBox, CHL1Item );
+
+ void Spawn( void );
+ void Precache( void );
+ bool KeyValue( const char *szKeyName, const char *szValue );
+ void BoxTouch( CBaseEntity *pPlayer );
+
+ DECLARE_DATADESC();
+
+private:
+ bool PackAmmo( char *szName, int iCount );
+ int GiveAmmo( int iCount, char *szName, int iMax, int *pIndex = NULL );
+
+ int m_cAmmoTypes; // how many ammo types packed into this box (if packed by a level designer)
+ string_t m_rgiszAmmo[MAX_AMMO_SLOTS]; // ammo names
+ int m_rgAmmo[MAX_AMMO_SLOTS]; // ammo quantities
+};
+LINK_ENTITY_TO_CLASS(weaponbox, CWeaponBox);
+PRECACHE_REGISTER(weaponbox);
+
+BEGIN_DATADESC( CWeaponBox )
+ DEFINE_ARRAY( m_rgiszAmmo, FIELD_STRING, MAX_AMMO_SLOTS ),
+ DEFINE_ARRAY( m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ),
+ DEFINE_FIELD( m_cAmmoTypes, FIELD_INTEGER ),
+
+ DEFINE_ENTITYFUNC( BoxTouch ),
+END_DATADESC()
+
+
+bool CWeaponBox::KeyValue( const char *szKeyName, const char *szValue )
+{
+ if ( m_cAmmoTypes < MAX_AMMO_SLOTS )
+ {
+ if ( PackAmmo( (char *)szKeyName, atoi( szValue ) ) )
+ {
+ m_cAmmoTypes++;// count this new ammo type.
+
+ return true;
+ }
+ }
+ else
+ {
+ Warning( "WeaponBox too full! only %d ammotypes allowed\n", MAX_AMMO_SLOTS );
+ }
+
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
+
+void CWeaponBox::Spawn( void )
+{
+ Precache();
+ SetModel( WEAPONBOX_MODEL );
+ BaseClass::Spawn();
+
+ PrecacheScriptSound( "Item.Pickup" );
+
+ SetTouch( &CWeaponBox::BoxTouch );
+}
+
+
+void CWeaponBox::Precache( void )
+{
+ PrecacheModel( WEAPONBOX_MODEL );
+}
+
+
+void CWeaponBox::BoxTouch( CBaseEntity *pOther )
+{
+ if ( !( GetFlags() & FL_ONGROUND ) )
+ {
+ return;
+ }
+
+ if ( !pOther->IsPlayer() )
+ {
+ // only players may touch a weaponbox.
+ return;
+ }
+
+ if ( !pOther->IsAlive() )
+ {
+ // no dead guys.
+ return;
+ }
+
+ CBasePlayer *pPlayer = (CBasePlayer *)pOther;
+ int i;
+
+// dole out ammo
+ for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ )
+ {
+ if ( m_rgiszAmmo[ i ] != NULL_STRING )
+ {
+ // there's some ammo of this type.
+ pPlayer->GiveAmmo( m_rgAmmo[ i ], (char *)STRING( m_rgiszAmmo[ i ] ) );
+
+ // now empty the ammo from the weaponbox since we just gave it to the player
+ m_rgiszAmmo[ i ] = NULL_STRING;
+ m_rgAmmo[ i ] = 0;
+ }
+ }
+
+ CPASAttenuationFilter filter( pOther, "Item.Pickup" );
+ EmitSound( filter, pOther->entindex(), "Item.Pickup" );
+
+ SetTouch(NULL);
+ if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_NO )
+ {
+ UTIL_Remove( this );
+ }
+}
+
+
+bool CWeaponBox::PackAmmo( char *szName, int iCount )
+{
+ char szConvertedName[ 32 ];
+
+ if ( FStrEq( szName, "" ) )
+ {
+ // error here
+ Warning( "NULL String in PackAmmo!\n" );
+ return false;
+ }
+
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "%s", szName );
+ if ( !stricmp( szName, "bolts" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "XBowBolt" );
+ }
+ if ( !stricmp( szName, "uranium" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "Uranium" );
+ }
+ if ( !stricmp( szName, "9mm" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "9mmRound" );
+ }
+ if ( !stricmp( szName, "Hand Grenade" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "Grenade" );
+ }
+ if ( !stricmp( szName, "Hornets" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "Hornet" );
+ }
+ if ( !stricmp( szName, "ARgrenades" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "MP5_Grenade" );
+ }
+ if ( !stricmp( szName, "357" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "357Round" );
+ }
+ if ( !stricmp( szName, "rockets" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "RPG_Rocket" );
+ }
+ if ( !stricmp( szName, "Satchel Charge" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "Satchel" );
+ }
+ if ( !stricmp( szName, "buckshot" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "Buckshot" );
+ }
+ if ( !stricmp( szName, "Snarks" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "Snark" );
+ }
+ if ( !stricmp( szName, "Trip Mine" ) )
+ {
+ Q_snprintf( szConvertedName, sizeof( szConvertedName ), "TripMine" );
+ }
+
+ int iMaxCarry = GetAmmoDef()->MaxCarry( GetAmmoDef()->Index( szConvertedName ) );
+
+ if ( iMaxCarry > 0 && iCount > 0 )
+ {
+ //ALERT ( at_console, "Packed %d rounds of %s\n", iCount, STRING(iszName) );
+ GiveAmmo( iCount, szConvertedName, iMaxCarry );
+ return true;
+ }
+
+ return false;
+}
+
+//=========================================================
+// CWeaponBox - GiveAmmo
+//=========================================================
+int CWeaponBox::GiveAmmo( int iCount, char *szName, int iMax, int *pIndex )
+{
+ int i;
+
+ for ( i = 1; ( i < MAX_AMMO_SLOTS ) && ( m_rgiszAmmo[i] != NULL_STRING ); i++ )
+ {
+ if ( stricmp( szName, STRING( m_rgiszAmmo[i] ) ) == 0 )
+ {
+ if (pIndex)
+ *pIndex = i;
+
+ int iAdd = MIN( iCount, iMax - m_rgAmmo[i]);
+ if (iCount == 0 || iAdd > 0)
+ {
+ m_rgAmmo[i] += iAdd;
+
+ return i;
+ }
+ return -1;
+ }
+ }
+
+ if (i < MAX_AMMO_SLOTS)
+ {
+ if (pIndex)
+ *pIndex = i;
+
+ m_rgiszAmmo[i] = AllocPooledString( szName );
+ m_rgAmmo[i] = iCount;
+
+ return i;
+ }
+ Warning( "out of named ammo slots\n");
+ return i;
+}
diff --git a/game/server/hl1/hl1mp_bot_temp.cpp b/game/server/hl1/hl1mp_bot_temp.cpp
new file mode 100644
index 0000000..d5eae12
--- /dev/null
+++ b/game/server/hl1/hl1mp_bot_temp.cpp
@@ -0,0 +1,432 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Basic BOT handling.
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+
+#include "cbase.h"
+#include "player.h"
+#include "hl1mp_player.h"
+#include "in_buttons.h"
+#include "movehelper_server.h"
+
+void ClientPutInServer( edict_t *pEdict, const char *playername );
+void Bot_Think( CHL1MP_Player *pBot );
+
+#ifdef DEBUG
+
+ConVar bot_forcefireweapon( "bot_forcefireweapon", "", 0, "Force bots with the specified weapon to fire." );
+ConVar bot_forceattack2( "bot_forceattack2", "0", 0, "When firing, use attack2." );
+ConVar bot_forceattackon( "bot_forceattackon", "0", 0, "When firing, don't tap fire, hold it down." );
+ConVar bot_flipout( "bot_flipout", "0", 0, "When on, all bots fire their guns." );
+ConVar bot_defend( "bot_defend", "0", 0, "Set to a team number, and that team will all keep their combat shields raised." );
+ConVar bot_changeclass( "bot_changeclass", "0", 0, "Force all bots to change to the specified class." );
+ConVar bot_zombie( "bot_zombie", "0", 0, "Brraaaaaiiiins." );
+static ConVar bot_mimic( "bot_mimic", "0", 0, "Bot uses usercmd of player by index." );
+static ConVar bot_mimic_yaw_offset( "bot_mimic_yaw_offset", "0", 0, "Offsets the bot yaw." );
+ConVar bot_attack( "bot_attack", "1", 0, "Shoot!" );
+
+ConVar bot_sendcmd( "bot_sendcmd", "", 0, "Forces bots to send the specified command." );
+
+ConVar bot_crouch( "bot_crouch", "0", 0, "Bot crouches" );
+
+static int BotNumber = 1;
+static int g_iNextBotTeam = -1;
+static int g_iNextBotClass = -1;
+
+typedef struct
+{
+ bool backwards;
+
+ float nextturntime;
+ bool lastturntoright;
+
+ float nextstrafetime;
+ float sidemove;
+
+ QAngle forwardAngle;
+ QAngle lastAngles;
+
+ float m_flJoinTeamTime;
+ int m_WantedTeam;
+ int m_WantedClass;
+} botdata_t;
+
+static botdata_t g_BotData[ MAX_PLAYERS ];
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a new Bot and put it in the game.
+// Output : Pointer to the new Bot, or NULL if there's no free clients.
+//-----------------------------------------------------------------------------
+CBasePlayer *BotPutInServer( bool bFrozen, int iTeam )
+{
+ g_iNextBotTeam = iTeam;
+
+ char botname[ 64 ];
+ Q_snprintf( botname, sizeof( botname ), "Bot%02i", BotNumber );
+
+ // This is an evil hack, but we use it to prevent sv_autojointeam from kicking in.
+
+ edict_t *pEdict = engine->CreateFakeClient( botname );
+
+ if (!pEdict)
+ {
+ Msg( "Failed to create Bot.\n");
+ return NULL;
+ }
+
+ // Allocate a CBasePlayer for the bot, and call spawn
+ //ClientPutInServer( pEdict, botname );
+ CHL1MP_Player *pPlayer = ((CHL1MP_Player *)CBaseEntity::Instance( pEdict ));
+ pPlayer->ClearFlags();
+ pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT );
+
+ if ( bFrozen )
+ pPlayer->AddEFlags( EFL_BOT_FROZEN );
+
+ char szReturnString[512];
+
+ Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", "gman" );
+ engine->ClientCommand ( pPlayer->edict(), szReturnString );
+
+ BotNumber++;
+
+ g_BotData[pPlayer->entindex()-1].m_WantedTeam = iTeam;
+ g_BotData[pPlayer->entindex()-1].m_flJoinTeamTime = gpGlobals->curtime + 0.3;
+
+ return pPlayer;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Run through all the Bots in the game and let them think.
+//-----------------------------------------------------------------------------
+void Bot_RunAll( void )
+{
+ for ( int i = 1; i <= gpGlobals->maxClients; i++ )
+ {
+ CHL1MP_Player *pPlayer = ToHL1MPPlayer( UTIL_PlayerByIndex( i ) );
+
+ if ( pPlayer && (pPlayer->GetFlags() & FL_FAKECLIENT) )
+ {
+ Bot_Think( pPlayer );
+ }
+ }
+}
+
+bool RunMimicCommand( CUserCmd& cmd )
+{
+ if ( bot_mimic.GetInt() <= 0 )
+ return false;
+
+ if ( bot_mimic.GetInt() > gpGlobals->maxClients )
+ return false;
+
+
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( bot_mimic.GetInt() );
+ if ( !pPlayer )
+ return false;
+
+ if ( !pPlayer->GetLastUserCommand() )
+ return false;
+
+ cmd = *pPlayer->GetLastUserCommand();
+ cmd.viewangles[YAW] += bot_mimic_yaw_offset.GetFloat();
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Simulates a single frame of movement for a player
+// Input : *fakeclient -
+// *viewangles -
+// forwardmove -
+// sidemove -
+// upmove -
+// buttons -
+// impulse -
+// msec -
+// Output : virtual void
+//-----------------------------------------------------------------------------
+static void RunPlayerMove( CHL1MP_Player *fakeclient, const QAngle& viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, float frametime )
+{
+ if ( !fakeclient )
+ return;
+
+ CUserCmd cmd;
+
+ // Store off the globals.. they're gonna get whacked
+ float flOldFrametime = gpGlobals->frametime;
+ float flOldCurtime = gpGlobals->curtime;
+
+ float flTimeBase = gpGlobals->curtime + gpGlobals->frametime - frametime;
+ fakeclient->SetTimeBase( flTimeBase );
+
+ Q_memset( &cmd, 0, sizeof( cmd ) );
+
+ if ( !RunMimicCommand( cmd ) && !bot_zombie.GetBool() )
+ {
+ VectorCopy( viewangles, cmd.viewangles );
+ cmd.forwardmove = forwardmove;
+ cmd.sidemove = sidemove;
+ cmd.upmove = upmove;
+ cmd.buttons = buttons;
+ cmd.impulse = impulse;
+ cmd.random_seed = random->RandomInt( 0, 0x7fffffff );
+ }
+
+ if( bot_crouch.GetInt() )
+ cmd.buttons |= IN_DUCK;
+
+ if ( bot_attack.GetBool() )
+ cmd.buttons |= IN_ATTACK;
+
+ MoveHelperServer()->SetHost( fakeclient );
+ fakeclient->PlayerRunCommand( &cmd, MoveHelperServer() );
+
+ // save off the last good usercmd
+ fakeclient->SetLastUserCommand( cmd );
+
+ // Clear out any fixangle that has been set
+ fakeclient->pl.fixangle = FIXANGLE_NONE;
+
+ // Restore the globals..
+ gpGlobals->frametime = flOldFrametime;
+ gpGlobals->curtime = flOldCurtime;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Run this Bot's AI for one frame.
+//-----------------------------------------------------------------------------
+void Bot_Think( CHL1MP_Player *pBot )
+{
+ // Make sure we stay being a bot
+ pBot->AddFlag( FL_FAKECLIENT );
+
+ botdata_t *botdata = &g_BotData[ ENTINDEX( pBot->edict() ) - 1 ];
+
+ QAngle vecViewAngles;
+ float forwardmove = 0.0;
+ float sidemove = botdata->sidemove;
+ float upmove = 0.0;
+ unsigned short buttons = 0;
+ byte impulse = 0;
+ float frametime = gpGlobals->frametime;
+
+ vecViewAngles = pBot->GetLocalAngles();
+
+
+ // Create some random values
+ if ( pBot->IsAlive() && (pBot->GetSolid() == SOLID_BBOX) )
+ {
+ trace_t trace;
+
+ // Stop when shot
+ if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) )
+ {
+ if ( pBot->m_iHealth == 100 )
+ {
+ forwardmove = 600 * ( botdata->backwards ? -1 : 1 );
+ if ( botdata->sidemove != 0.0f )
+ {
+ forwardmove *= random->RandomFloat( 0.1, 1.0f );
+ }
+ }
+ else
+ {
+ forwardmove = 0;
+ }
+ }
+
+ // Only turn if I haven't been hurt
+ if ( !pBot->IsEFlagSet(EFL_BOT_FROZEN) && pBot->m_iHealth == 100 )
+ {
+ Vector vecEnd;
+ Vector forward;
+
+ QAngle angle;
+ float angledelta = 15.0;
+
+ int maxtries = (int)360.0/angledelta;
+
+ if ( botdata->lastturntoright )
+ {
+ angledelta = -angledelta;
+ }
+
+ angle = pBot->GetLocalAngles();
+
+ Vector vecSrc;
+ while ( --maxtries >= 0 )
+ {
+ AngleVectors( angle, &forward );
+
+ vecSrc = pBot->GetLocalOrigin() + Vector( 0, 0, 36 );
+
+ vecEnd = vecSrc + forward * 10;
+
+ UTIL_TraceHull( vecSrc, vecEnd, VEC_HULL_MIN_SCALED( pBot ), VEC_HULL_MAX_SCALED( pBot ),
+ MASK_PLAYERSOLID, pBot, COLLISION_GROUP_NONE, &trace );
+
+ if ( trace.fraction == 1.0 )
+ {
+ if ( gpGlobals->curtime < botdata->nextturntime )
+ {
+ break;
+ }
+ }
+
+ angle.y += angledelta;
+
+ if ( angle.y > 180 )
+ angle.y -= 360;
+ else if ( angle.y < -180 )
+ angle.y += 360;
+
+ botdata->nextturntime = gpGlobals->curtime + 2.0;
+ botdata->lastturntoright = random->RandomInt( 0, 1 ) == 0 ? true : false;
+
+ botdata->forwardAngle = angle;
+ botdata->lastAngles = angle;
+
+ }
+
+
+ if ( gpGlobals->curtime >= botdata->nextstrafetime )
+ {
+ botdata->nextstrafetime = gpGlobals->curtime + 1.0f;
+
+ if ( random->RandomInt( 0, 5 ) == 0 )
+ {
+ botdata->sidemove = -600.0f + 1200.0f * random->RandomFloat( 0, 2 );
+ }
+ else
+ {
+ botdata->sidemove = 0;
+ }
+ sidemove = botdata->sidemove;
+
+ if ( random->RandomInt( 0, 20 ) == 0 )
+ {
+ botdata->backwards = true;
+ }
+ else
+ {
+ botdata->backwards = false;
+ }
+ }
+
+ pBot->SetLocalAngles( angle );
+ vecViewAngles = angle;
+ }
+
+ // Is my team being forced to defend?
+ if ( bot_defend.GetInt() == pBot->GetTeamNumber() )
+ {
+ buttons |= IN_ATTACK2;
+ }
+ // If bots are being forced to fire a weapon, see if I have it
+ else if ( bot_forcefireweapon.GetString() )
+ {
+ CBaseCombatWeapon *pWeapon = pBot->Weapon_OwnsThisType( bot_forcefireweapon.GetString() );
+ if ( pWeapon )
+ {
+ // Switch to it if we don't have it out
+ CBaseCombatWeapon *pActiveWeapon = pBot->GetActiveWeapon();
+
+ // Switch?
+ if ( pActiveWeapon != pWeapon )
+ {
+ pBot->Weapon_Switch( pWeapon );
+ }
+ else
+ {
+ // Start firing
+ // Some weapons require releases, so randomise firing
+ if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
+ {
+ buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
+ }
+ }
+ }
+ }
+
+ if ( bot_flipout.GetInt() )
+ {
+ if ( bot_forceattackon.GetBool() || (RandomFloat(0.0,1.0) > 0.5) )
+ {
+ buttons |= bot_forceattack2.GetBool() ? IN_ATTACK2 : IN_ATTACK;
+ }
+ }
+
+ if ( strlen( bot_sendcmd.GetString() ) > 0 )
+ {
+ //send the cmd from this bot
+ CCommand args;
+ args.Tokenize( bot_sendcmd.GetString() );
+ pBot->ClientCommand( args );
+
+ bot_sendcmd.SetValue("");
+ }
+ }
+ else
+ {
+ // Wait for Reinforcement wave
+ if ( !pBot->IsAlive() )
+ {
+ // Try hitting my buttons occasionally
+ if ( random->RandomInt( 0, 100 ) > 80 )
+ {
+ // Respawn the bot
+ if ( random->RandomInt( 0, 1 ) == 0 )
+ {
+ buttons |= IN_JUMP;
+ }
+ else
+ {
+ buttons = 0;
+ }
+ }
+ }
+ }
+
+ if ( bot_flipout.GetInt() >= 2 )
+ {
+
+ QAngle angOffset = RandomAngle( -1, 1 );
+
+ botdata->lastAngles += angOffset;
+
+ for ( int i = 0 ; i < 2; i++ )
+ {
+ if ( fabs( botdata->lastAngles[ i ] - botdata->forwardAngle[ i ] ) > 15.0f )
+ {
+ if ( botdata->lastAngles[ i ] > botdata->forwardAngle[ i ] )
+ {
+ botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] + 15;
+ }
+ else
+ {
+ botdata->lastAngles[ i ] = botdata->forwardAngle[ i ] - 15;
+ }
+ }
+ }
+
+ botdata->lastAngles[ 2 ] = 0;
+
+ pBot->SetLocalAngles( botdata->lastAngles );
+ }
+
+ RunPlayerMove( pBot, pBot->GetLocalAngles(), forwardmove, sidemove, upmove, buttons, impulse, frametime );
+}
+
+#endif
+
diff --git a/game/server/hl1/hl1mp_bot_temp.h b/game/server/hl1/hl1mp_bot_temp.h
new file mode 100644
index 0000000..f78e63e
--- /dev/null
+++ b/game/server/hl1/hl1mp_bot_temp.h
@@ -0,0 +1,19 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+#ifndef BOT_BASE_H
+#define BOT_BASE_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+
+// If iTeam or iClass is -1, then a team or class is randomly chosen.
+CBasePlayer *BotPutInServer( bool bFrozen, int iTeam );
+
+
+#endif // BOT_BASE_H
+
diff --git a/game/server/hl1/hl1mp_gameinterface.cpp b/game/server/hl1/hl1mp_gameinterface.cpp
new file mode 100644
index 0000000..afbe164
--- /dev/null
+++ b/game/server/hl1/hl1mp_gameinterface.cpp
@@ -0,0 +1,28 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "cbase.h"
+#include "gameinterface.h"
+#include "mapentities.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+void CServerGameClients::GetPlayerLimits( int& minplayers, int& maxplayers, int &defaultMaxPlayers ) const
+{
+ minplayers = defaultMaxPlayers = 8;
+ maxplayers = MAX_PLAYERS - 1;
+}
+
+
+// -------------------------------------------------------------------------------------------- //
+// Mod-specific CServerGameDLL implementation.
+// -------------------------------------------------------------------------------------------- //
+
+void CServerGameDLL::LevelInit_ParseAllEntities( const char *pMapEntities )
+{
+ MapEntity_ParseAllEntities( pMapEntities, NULL );
+}
diff --git a/game/server/hl1/hl1mp_player.cpp b/game/server/hl1/hl1mp_player.cpp
new file mode 100644
index 0000000..84adbfe
--- /dev/null
+++ b/game/server/hl1/hl1mp_player.cpp
@@ -0,0 +1,636 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Multiplayer Player for HL1.
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "hl1mp_player.h"
+#include "client.h"
+#include "team.h"
+
+class CTEPlayerAnimEvent : public CBaseTempEntity
+{
+public:
+ DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity );
+ DECLARE_SERVERCLASS();
+
+ CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name )
+ {
+ }
+
+ CNetworkHandle( CBasePlayer, m_hPlayer );
+ CNetworkVar( int, m_iEvent );
+ CNetworkVar( int, m_nData );
+};
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent )
+ SendPropEHandle( SENDINFO( m_hPlayer ) ),
+ SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ),
+ SendPropInt( SENDINFO( m_nData ), 32 )
+END_SEND_TABLE()
+
+static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" );
+
+void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData )
+{
+ CPVSFilter filter( pPlayer->EyePosition() );
+
+ // The player himself doesn't need to be sent his animation events
+ // unless cs_showanimstate wants to show them.
+// if ( !ToolsEnabled() && ( cl_showanimstate.GetInt() == pPlayer->entindex() ) )
+ {
+// filter.RemoveRecipient( pPlayer );
+ }
+
+ g_TEPlayerAnimEvent.m_hPlayer = pPlayer;
+ g_TEPlayerAnimEvent.m_iEvent = event;
+ g_TEPlayerAnimEvent.m_nData = nData;
+ g_TEPlayerAnimEvent.Create( filter, 0 );
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+
+extern int gEvilImpulse101;
+
+LINK_ENTITY_TO_CLASS( player_mp, CHL1MP_Player );
+PRECACHE_REGISTER( player_mp );
+
+IMPLEMENT_SERVERCLASS_ST( CHL1MP_Player, DT_HL1MP_PLAYER )
+ SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
+ SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
+ SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
+ SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
+ SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
+
+ // cs_playeranimstate and clientside animation takes care of these on the client
+// SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
+ SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
+
+ SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11 ),
+ SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11 ),
+
+ SendPropEHandle( SENDINFO( m_hRagdoll ) ),
+ SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ),
+ SendPropInt( SENDINFO( m_iRealSequence ), 9 ),
+
+
+// SendPropDataTable( SENDINFO_DT( m_Shared ), &REFERENCE_SEND_TABLE( DT_TFCPlayerShared ) )
+END_SEND_TABLE()
+
+void cc_CreatePredictionError_f()
+{
+ CBaseEntity *pEnt = CBaseEntity::Instance( 1 );
+ pEnt->SetAbsOrigin( pEnt->GetAbsOrigin() + Vector( 63, 0, 0 ) );
+}
+
+ConCommand cc_CreatePredictionError( "CreatePredictionError", cc_CreatePredictionError_f, "Create a prediction error", FCVAR_CHEAT );
+
+static const char * s_szModelPath = "models/player/mp/";
+
+CHL1MP_Player::CHL1MP_Player()
+{
+ m_PlayerAnimState = CreatePlayerAnimState( this );
+// item_list = 0;
+
+ UseClientSideAnimation();
+ m_angEyeAngles.Init();
+// m_pCurStateInfo = NULL;
+ m_lifeState = LIFE_DEAD; // Start "dead".
+
+ m_iSpawnInterpCounter = 0;
+ m_flNextModelChangeTime = 0;
+ m_flNextTeamChangeTime = 0;
+
+// SetViewOffset( TFC_PLAYER_VIEW_OFFSET );
+
+// SetContextThink( &CTFCPlayer::TFCPlayerThink, gpGlobals->curtime, "TFCPlayerThink" );
+}
+
+CHL1MP_Player::~CHL1MP_Player()
+{
+ m_PlayerAnimState->Release();
+}
+
+void CHL1MP_Player::PostThink( void )
+{
+ BaseClass::PostThink();
+
+ QAngle angles = GetLocalAngles();
+ angles[PITCH] = 0;
+ SetLocalAngles( angles );
+
+ // Store the eye angles pitch so the client can compute its animation state correctly.
+ m_angEyeAngles = EyeAngles();
+
+ m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
+}
+
+void CHL1MP_Player::Spawn( void )
+{
+ if ( !IsObserver() )
+ {
+ RemoveEffects( EF_NODRAW );
+ SetMoveType( MOVETYPE_WALK );
+ RemoveSolidFlags( FSOLID_NOT_SOLID );
+
+ // if no model, force one
+ if ( !GetModelPtr() )
+ SetModel( "models/player/mp/gordon/gordon.mdl" );
+ }
+
+ m_flNextModelChangeTime = 0;
+ m_flNextTeamChangeTime = 0;
+
+ BaseClass::Spawn();
+
+ if ( !IsObserver() )
+ {
+ GiveDefaultItems();
+ SetPlayerModel();
+ }
+
+ m_bHasLongJump = false;
+
+ m_iSpawnInterpCounter = (m_iSpawnInterpCounter + 1) % 8;
+}
+
+void CHL1MP_Player::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
+{
+ m_PlayerAnimState->DoAnimationEvent( event, nData );
+ TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy.
+}
+
+void CHL1MP_Player::GiveDefaultItems( void )
+{
+ GiveNamedItem( "weapon_crowbar" );
+ GiveNamedItem( "weapon_glock" );
+
+ CBasePlayer::GiveAmmo( 68, "9mmRound" );
+}
+
+void CHL1MP_Player::UpdateOnRemove( void )
+{
+ if ( m_hRagdoll )
+ {
+ UTIL_RemoveImmediate( m_hRagdoll );
+ m_hRagdoll = NULL;
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+
+void CHL1MP_Player::DetonateSatchelCharges( void )
+{
+ CBaseEntity *pSatchel = NULL;
+
+ while ( (pSatchel = gEntList.FindEntityByClassname( pSatchel, "monster_satchel" ) ) != NULL)
+ {
+ if ( pSatchel->GetOwnerEntity() == this )
+ {
+ pSatchel->Use( this, this, USE_ON, 0 );
+ }
+ }
+}
+
+void CHL1MP_Player::Event_Killed( const CTakeDamageInfo &info )
+{
+ DoAnimationEvent( PLAYERANIMEVENT_DIE );
+// SetNumAnimOverlays( 0 );
+
+
+ // Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW
+ // because we still want to transmit to the clients in our PVS.
+ if ( !IsHLTV() )
+ CreateRagdollEntity();
+
+ DetonateSatchelCharges();
+
+ BaseClass::Event_Killed( info );
+
+ m_lifeState = LIFE_DEAD;
+ RemoveEffects( EF_NODRAW ); // still draw player body
+}
+
+
+void CHL1MP_Player::SetAnimation( PLAYER_ANIM playerAnim )
+{
+// BaseClass::SetAnimation( playerAnim );
+ if ( playerAnim == PLAYER_ATTACK1 )
+ {
+ DoAnimationEvent( PLAYERANIMEVENT_FIRE_GUN );
+ }
+
+ int animDesired;
+ char szAnim[64];
+
+ float speed;
+
+ speed = GetAbsVelocity().Length2D();
+
+ if (GetFlags() & (FL_FROZEN|FL_ATCONTROLS))
+ {
+ speed = 0;
+ playerAnim = PLAYER_IDLE;
+ }
+
+ if ( playerAnim == PLAYER_ATTACK1 )
+ {
+ if ( speed > 0 )
+ {
+ playerAnim = PLAYER_WALK;
+ }
+ else
+ {
+ playerAnim = PLAYER_IDLE;
+ }
+ }
+
+ Activity idealActivity = ACT_WALK;// TEMP!!!!!
+
+ // This could stand to be redone. Why is playerAnim abstracted from activity? (sjb)
+ if (playerAnim == PLAYER_JUMP)
+ {
+ idealActivity = ACT_HOP;
+ }
+ else if (playerAnim == PLAYER_SUPERJUMP)
+ {
+ idealActivity = ACT_LEAP;
+ }
+ else if (playerAnim == PLAYER_DIE)
+ {
+ if ( m_lifeState == LIFE_ALIVE )
+ {
+ idealActivity = ACT_DIERAGDOLL;
+ }
+ }
+ else if (playerAnim == PLAYER_ATTACK1)
+ {
+ if ( GetActivity() == ACT_HOVER ||
+ GetActivity() == ACT_SWIM ||
+ GetActivity() == ACT_HOP ||
+ GetActivity() == ACT_LEAP ||
+ GetActivity() == ACT_DIESIMPLE )
+ {
+ idealActivity = GetActivity();
+ }
+ else
+ {
+ idealActivity = ACT_RANGE_ATTACK1;
+ }
+ }
+ else if (playerAnim == PLAYER_IDLE || playerAnim == PLAYER_WALK)
+ {
+ if ( !( GetFlags() & FL_ONGROUND ) && (GetActivity() == ACT_HOP || GetActivity() == ACT_LEAP) ) // Still jumping
+ {
+ idealActivity = GetActivity();
+ }
+ else if ( GetWaterLevel() > 1 )
+ {
+ if ( speed == 0 )
+ idealActivity = ACT_HOVER;
+ else
+ idealActivity = ACT_SWIM;
+ }
+ else if ( speed > 0 )
+ {
+ idealActivity = ACT_WALK;
+ }
+ else
+ {
+ idealActivity = ACT_IDLE;
+ }
+ }
+
+
+ if (idealActivity == ACT_RANGE_ATTACK1)
+ {
+ if ( GetFlags() & FL_DUCKING ) // crouching
+ {
+ Q_strncpy( szAnim, "crouch_shoot_" ,sizeof(szAnim));
+ }
+ else
+ {
+ Q_strncpy( szAnim, "ref_shoot_" ,sizeof(szAnim));
+ }
+ Q_strncat( szAnim, m_szAnimExtension ,sizeof(szAnim), COPY_ALL_CHARACTERS );
+ animDesired = LookupSequence( szAnim );
+ if (animDesired == -1)
+ animDesired = 0;
+
+ if ( GetSequence() != animDesired || !SequenceLoops() )
+ {
+ SetCycle( 0 );
+ }
+
+ // Tracker 24588: In single player when firing own weapon this causes eye and punchangle to jitter
+ //if (!SequenceLoops())
+ //{
+ // IncrementInterpolationFrame();
+ //}
+
+ SetActivity( idealActivity );
+ ResetSequence( animDesired );
+ }
+ else if (idealActivity == ACT_IDLE)
+ {
+ if ( GetFlags() & FL_DUCKING )
+ {
+ animDesired = LookupSequence( "crouch_idle" );
+ }
+ else
+ {
+ animDesired = LookupSequence( "look_idle" );
+ }
+ if (animDesired == -1)
+ animDesired = 0;
+
+ SetActivity( ACT_IDLE );
+ }
+ else if ( idealActivity == ACT_WALK )
+ {
+ if ( GetFlags() & FL_DUCKING )
+ {
+ animDesired = SelectWeightedSequence( ACT_CROUCH );
+ SetActivity( ACT_CROUCH );
+ }
+ else
+ {
+ animDesired = SelectWeightedSequence( ACT_RUN );
+ SetActivity( ACT_RUN );
+ }
+
+ }
+ else
+ {
+ if ( GetActivity() == idealActivity)
+ return;
+
+ SetActivity( idealActivity );
+
+ animDesired = SelectWeightedSequence( GetActivity() );
+
+ // Already using the desired animation?
+ if (GetSequence() == animDesired)
+ return;
+
+ m_iRealSequence = animDesired;
+ ResetSequence( animDesired );
+ SetCycle( 0 );
+ return;
+ }
+
+ // Already using the desired animation?
+ if (GetSequence() == animDesired)
+ return;
+
+ m_iRealSequence = animDesired;
+
+ //Msg( "Set animation to %d\n", animDesired );
+ // Reset to first frame of desired animation
+ ResetSequence( animDesired );
+ SetCycle( 0 );
+}
+
+static ConVar sv_debugweaponpickup( "sv_debugweaponpickup", "0", FCVAR_CHEAT, "Prints descriptive reasons as to why pickup did not work." );
+
+// correct respawning of weapons
+bool CHL1MP_Player::BumpWeapon( CBaseCombatWeapon *pWeapon )
+{ CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
+
+ // Can I have this weapon type?
+ if ( !IsAllowedToPickupWeapons() )
+ {
+ if ( sv_debugweaponpickup.GetBool() )
+ Msg("sv_debugweaponpickup: IsAllowedToPickupWeapons() returned false\n");
+
+ return false;
+ }
+
+ if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) )
+ {
+ if ( sv_debugweaponpickup.GetBool() && pOwner )
+ Msg("sv_debugweaponpickup: pOwner\n");
+
+ if ( sv_debugweaponpickup.GetBool() && !Weapon_CanUse( pWeapon ) )
+ Msg("sv_debugweaponpickup: Can't use weapon\n");
+
+ if ( sv_debugweaponpickup.GetBool() && !g_pGameRules->CanHavePlayerItem( this, pWeapon ) )
+ Msg("sv_debugweaponpickup: Gamerules says player can't have item\n");
+
+ if ( gEvilImpulse101 )
+ {
+ UTIL_Remove( pWeapon );
+ }
+ return false;
+ }
+
+ // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows)
+ if( !pWeapon->FVisible( this, MASK_SOLID ) && !(GetFlags() & FL_NOTARGET) )
+ {
+ if ( sv_debugweaponpickup.GetBool() && !FVisible( this, MASK_SOLID ) )
+ Msg("sv_debugweaponpickup: Can't fetch weapon through a wall\n");
+
+ if ( sv_debugweaponpickup.GetBool() && !(GetFlags() & FL_NOTARGET) )
+ Msg("sv_debugweaponpickup: NoTarget\n");
+
+ return false;
+ }
+
+ bool bOwnsWeaponAlready = !!Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType());
+
+ if ( bOwnsWeaponAlready == true )
+ {
+ //If we have room for the ammo, then "take" the weapon too.
+ if ( Weapon_EquipAmmoOnly( pWeapon ) )
+ {
+ pWeapon->CheckRespawn();
+
+ UTIL_Remove( pWeapon );
+
+ if ( sv_debugweaponpickup.GetBool() )
+ Msg("sv_debugweaponpickup: Picking up weapon\n");
+
+ return true;
+ }
+ else
+ {
+ if ( sv_debugweaponpickup.GetBool() )
+ Msg("sv_debugweaponpickup: Owns weapon already\n");
+
+ return false;
+ }
+ }
+
+ pWeapon->CheckRespawn();
+ Weapon_Equip( pWeapon );
+
+ if ( sv_debugweaponpickup.GetBool() )
+ Msg("sv_debugweaponpickup: Picking up weapon\n");
+
+ return true;
+}
+
+
+void CHL1MP_Player::ChangeTeam( int iTeamNum )
+{
+ bool bKill = false;
+
+ if ( g_pGameRules->IsTeamplay() == true )
+ {
+ if ( iTeamNum != GetTeamNumber() && GetTeamNumber() != TEAM_UNASSIGNED )
+ {
+ bKill = true;
+ }
+ }
+
+ BaseClass::ChangeTeam( iTeamNum );
+
+ m_flNextTeamChangeTime = gpGlobals->curtime + 5;
+
+ if ( g_pGameRules->IsTeamplay() == true )
+ {
+ SetPlayerTeamModel();
+ }
+ else
+ {
+ SetPlayerModel();
+ }
+
+ if ( bKill == true )
+ {
+ CommitSuicide();
+ }
+}
+
+void CHL1MP_Player::SetPlayerTeamModel( void )
+{
+ int iTeamNum = GetTeamNumber();
+
+ if ( iTeamNum <= TEAM_SPECTATOR )
+ return;
+
+ CTeam * pTeam = GetGlobalTeam( iTeamNum );
+
+ char szModelName[256];
+ Q_snprintf( szModelName, 256, "%s%s/%s.mdl", s_szModelPath, pTeam->GetName(), pTeam->GetName() );
+
+ // Check to see if the model was properly precached, do not error out if not.
+ int i = modelinfo->GetModelIndex( szModelName );
+ if ( i == -1 )
+ {
+ Warning("Model %s does not exist.\n", szModelName );
+ return;
+ }
+
+ SetModel( szModelName );
+ m_flNextModelChangeTime = gpGlobals->curtime + 5;
+}
+
+
+void CHL1MP_Player::SetPlayerModel( void )
+{
+ char szBaseName[128];
+ Q_FileBase( engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" ), szBaseName, 128 );
+
+ // Don't let it be 'none'; default to Barney
+ if ( Q_stricmp( "none", szBaseName ) == 0 )
+ {
+ Q_strcpy( szBaseName, "gordon" );
+ }
+
+ char szModelName[256];
+ Q_snprintf( szModelName, 256, "%s%s/%s.mdl", s_szModelPath, szBaseName, szBaseName );
+
+ // Check to see if the model was properly precached, do not error out if not.
+ int i = modelinfo->GetModelIndex( szModelName );
+ if ( i == -1 )
+ {
+ SetModel( "models/player/mp/gordon/gordon.mdl" );
+ engine->ClientCommand ( edict(), "cl_playermodel models/gordon.mdl\n" );
+ return;
+ }
+
+ SetModel( szModelName );
+
+ m_flNextModelChangeTime = gpGlobals->curtime + 5;
+}
+
+
+// -------------------------------------------------------------------------------- //
+// Ragdoll entities.
+// -------------------------------------------------------------------------------- //
+
+class CHL1MPRagdoll : public CBaseAnimatingOverlay
+{
+public:
+ DECLARE_CLASS( CHL1MPRagdoll, CBaseAnimatingOverlay );
+ DECLARE_SERVERCLASS();
+
+ // Transmit ragdolls to everyone.
+ virtual int UpdateTransmitState()
+ {
+ return SetTransmitState( FL_EDICT_ALWAYS );
+ }
+
+public:
+ // In case the client has the player entity, we transmit the player index.
+ // In case the client doesn't have it, we transmit the player's model index, origin, and angles
+ // so they can create a ragdoll in the right place.
+ CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle
+ CNetworkVector( m_vecRagdollVelocity );
+ CNetworkVector( m_vecRagdollOrigin );
+};
+
+LINK_ENTITY_TO_CLASS( hl1mp_ragdoll, CHL1MPRagdoll );
+
+IMPLEMENT_SERVERCLASS_ST_NOBASE( CHL1MPRagdoll, DT_HL1MPRagdoll )
+ SendPropVector ( SENDINFO( m_vecRagdollOrigin), -1, SPROP_COORD ),
+ SendPropEHandle ( SENDINFO( m_hPlayer ) ),
+ SendPropModelIndex( SENDINFO( m_nModelIndex ) ),
+ SendPropInt ( SENDINFO( m_nForceBone), 8, 0 ),
+ SendPropVector ( SENDINFO( m_vecForce), -1, SPROP_NOSCALE ),
+ SendPropVector ( SENDINFO( m_vecRagdollVelocity ) )
+END_SEND_TABLE()
+
+
+void CHL1MP_Player::CreateRagdollEntity( void )
+{
+ if ( m_hRagdoll )
+ {
+ UTIL_RemoveImmediate( m_hRagdoll );
+ m_hRagdoll = NULL;
+ }
+
+ // If we already have a ragdoll, don't make another one.
+ CHL1MPRagdoll *pRagdoll = dynamic_cast< CHL1MPRagdoll* >(m_hRagdoll.Get() );
+
+ if ( !pRagdoll )
+ {
+ // Create a new one
+ pRagdoll = dynamic_cast< CHL1MPRagdoll* >( CreateEntityByName( "hl1mp_ragdoll" ) );
+ }
+
+ if ( pRagdoll )
+ {
+ pRagdoll->m_hPlayer = this;
+ pRagdoll->m_vecRagdollOrigin = GetAbsOrigin();
+ pRagdoll->m_vecRagdollVelocity = GetAbsVelocity();
+ pRagdoll->m_nModelIndex = m_nModelIndex;
+ pRagdoll->m_nForceBone = m_nForceBone;
+ //pRagdoll->m_vecForce = m_vecTotalBulletForce;
+ pRagdoll->SetAbsOrigin( GetAbsOrigin() );
+
+ }
+
+ m_hRagdoll = pRagdoll;
+}
+
+void CHL1MP_Player::CreateCorpse( void )
+{
+
+}
+
diff --git a/game/server/hl1/hl1mp_player.h b/game/server/hl1/hl1mp_player.h
new file mode 100644
index 0000000..5ada43f
--- /dev/null
+++ b/game/server/hl1/hl1mp_player.h
@@ -0,0 +1,87 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#ifndef HL1MP_PLAYER_H
+#define HL1MP_PLAYER_H
+#pragma once
+
+#include "cbase.h"
+#include "hl1_player_shared.h"
+#include "hl1_player.h"
+#include "takedamageinfo.h"
+
+
+class CHL1MP_Player;
+
+
+//=============================================================================
+// >> HL1MP_Player
+//=============================================================================
+class CHL1MP_Player : public CHL1_Player
+{
+public:
+ DECLARE_CLASS( CHL1MP_Player, CHL1_Player );
+ DECLARE_SERVERCLASS();
+
+ CHL1MP_Player();
+ ~CHL1MP_Player( void );
+
+ virtual void Event_Killed( const CTakeDamageInfo &info );
+ virtual void Spawn( void );
+ virtual void PostThink( void );
+ virtual void SetAnimation( PLAYER_ANIM playerAnim );
+ void GiveDefaultItems( void );
+ void CreateRagdollEntity( void );
+ void UpdateOnRemove( void );
+ virtual bool BecomeRagdollOnClient( const Vector &force ) { return true; };
+ virtual void CreateCorpse( void );
+
+ virtual bool BumpWeapon( CBaseCombatWeapon *pWeapon );
+
+ virtual void ChangeTeam( int iTeamNum ) OVERRIDE;
+
+ void SetPlayerTeamModel( void );
+
+ float GetNextModelChangeTime( void ) { return m_flNextModelChangeTime; }
+ float GetNextTeamChangeTime( void ) { return m_flNextTeamChangeTime; }
+
+ void SetPlayerModel( void );
+
+ void DoAnimationEvent( PlayerAnimEvent_t event, int nData = 0 );
+
+ virtual bool StartObserverMode (int mode)
+ {
+ if ( !IsHLTV() )
+ return false;
+ return BaseClass::StartObserverMode( mode );
+ }
+
+ void DetonateSatchelCharges( void );
+
+ CNetworkVar( int, m_iRealSequence );
+
+private:
+ CNetworkHandle( CBaseEntity, m_hRagdoll );
+ CNetworkVar( int, m_iSpawnInterpCounter );
+ CNetworkQAngle( m_angEyeAngles );
+
+ IHL1MPPlayerAnimState* m_PlayerAnimState;
+ float m_flNextModelChangeTime;
+ float m_flNextTeamChangeTime;
+};
+
+inline CHL1MP_Player *ToHL1MPPlayer( CBaseEntity *pEntity )
+{
+ if ( !pEntity || !pEntity->IsPlayer() )
+ return NULL;
+
+ return dynamic_cast<CHL1MP_Player*>( pEntity );
+}
+
+
+#endif //HL1MP_PLAYER_H