diff options
Diffstat (limited to 'game/server/hl1/hl1_func_tank.cpp')
| -rw-r--r-- | game/server/hl1/hl1_func_tank.cpp | 1700 |
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 ); +} + |