summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_func_tank.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl1/hl1_func_tank.cpp')
-rw-r--r--game/server/hl1/hl1_func_tank.cpp1700
1 files changed, 1700 insertions, 0 deletions
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 );
+}
+