diff options
| author | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
|---|---|---|
| committer | Joe Ludwig <[email protected]> | 2013-06-26 15:22:04 -0700 |
| commit | 39ed87570bdb2f86969d4be821c94b722dc71179 (patch) | |
| tree | abc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/func_tank.cpp | |
| download | source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip | |
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/func_tank.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/func_tank.cpp | 4431 |
1 files changed, 4431 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/func_tank.cpp b/mp/src/game/server/hl2/func_tank.cpp new file mode 100644 index 00000000..b41708e9 --- /dev/null +++ b/mp/src/game/server/hl2/func_tank.cpp @@ -0,0 +1,4431 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "cbase.h"
+#include "func_tank.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 "physics_cannister.h"
+#include "decals.h"
+#include "shake.h"
+#include "particle_smokegrenade.h"
+#include "player.h"
+#include "entitylist.h"
+#include "IEffects.h"
+#include "ai_basenpc.h"
+#include "ai_behavior_functank.h"
+#include "weapon_rpg.h"
+#include "effects.h"
+#include "iservervehicle.h"
+#include "soundenvelope.h"
+#include "effect_dispatch_data.h"
+#include "te_effect_dispatch.h"
+#include "props.h"
+#include "rumble_shared.h"
+#include "particle_parse.h"
+// NVNT turret recoil
+#include "haptics/haptic_utils.h"
+
+#ifdef HL2_DLL
+#include "hl2_player.h"
+#endif //HL2_DLL
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint);
+
+ConVar mortar_visualize("mortar_visualize", "0" );
+
+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_FIELD( m_fireTime, FIELD_TIME ),
+ 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_FIELD( m_flMinRange2, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flMaxRange2, FIELD_FLOAT ),
+ DEFINE_KEYFIELD( m_iAmmoCount, FIELD_INTEGER, "ammo_count" ),
+ 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_FIELD( m_nBulletCount, FIELD_INTEGER ),
+ DEFINE_KEYFIELD( m_spread, FIELD_INTEGER, "firespread" ),
+ DEFINE_KEYFIELD( m_iBulletDamage, FIELD_INTEGER, "bullet_damage" ),
+ DEFINE_KEYFIELD( m_iBulletDamageVsPlayer, FIELD_INTEGER, "bullet_damage_vs_player" ),
+ DEFINE_KEYFIELD( m_iszMaster, FIELD_STRING, "master" ),
+
+#ifdef HL2_EPISODIC
+ DEFINE_KEYFIELD( m_iszAmmoType, FIELD_STRING, "ammotype" ),
+ DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+#else
+ DEFINE_FIELD( m_iSmallAmmoType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iMediumAmmoType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_iLargeAmmoType, FIELD_INTEGER ),
+#endif // HL2_EPISODIC
+
+ DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ),
+ DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ),
+ DEFINE_KEYFIELD( m_soundLoopRotate, FIELD_SOUNDNAME, "rotatesound" ),
+ DEFINE_KEYFIELD( m_flPlayerGracePeriod, FIELD_FLOAT, "playergraceperiod" ),
+ DEFINE_KEYFIELD( m_flIgnoreGraceUpto, FIELD_FLOAT, "ignoregraceupto" ),
+ DEFINE_KEYFIELD( m_flPlayerLockTimeBeforeFire, FIELD_FLOAT, "playerlocktimebeforefire" ),
+ DEFINE_FIELD( m_flLastSawNonPlayer, FIELD_TIME ),
+
+ DEFINE_FIELD( m_yawCenter, FIELD_FLOAT ),
+ DEFINE_FIELD( m_yawCenterWorld, FIELD_FLOAT ),
+ DEFINE_FIELD( m_pitchCenter, FIELD_FLOAT ),
+ DEFINE_FIELD( m_pitchCenterWorld, 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_POSITION_VECTOR ),
+ DEFINE_FIELD( m_hFuncTankTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_hController, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_vecControllerUsePos, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flNextAttack, FIELD_TIME ),
+ DEFINE_FIELD( m_targetEntityName, FIELD_STRING ),
+ DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_vTargetPosition, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecNPCIdleTarget, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_persist2burst, FIELD_FLOAT),
+ //DEFINE_FIELD( m_parentMatrix, FIELD_MATRIX ), // DON'T SAVE
+ DEFINE_FIELD( m_hControlVolume, FIELD_EHANDLE ),
+ DEFINE_KEYFIELD( m_iszControlVolume, FIELD_STRING, "control_volume" ),
+ DEFINE_FIELD( m_flNextControllerSearch, FIELD_TIME ),
+ DEFINE_FIELD( m_bShouldFindNPCs, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bNPCInRoute, FIELD_BOOLEAN ),
+ DEFINE_KEYFIELD( m_iszNPCManPoint, FIELD_STRING, "npc_man_point" ),
+ DEFINE_FIELD( m_bReadyToFire, FIELD_BOOLEAN ),
+
+ DEFINE_KEYFIELD( m_bPerformLeading, FIELD_BOOLEAN, "LeadTarget" ),
+ DEFINE_FIELD( m_flStartLeadFactor, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flStartLeadFactorTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextLeadFactor, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flNextLeadFactorTime, FIELD_TIME ),
+
+ // Used for when the gun is attached to another entity
+ DEFINE_KEYFIELD( m_iszBaseAttachment, FIELD_STRING, "gun_base_attach" ),
+ DEFINE_KEYFIELD( m_iszBarrelAttachment, FIELD_STRING, "gun_barrel_attach" ),
+// DEFINE_FIELD( m_nBarrelAttachment, FIELD_INTEGER ),
+
+ // Used when the gun is actually a part of the parent entity, and pose params aim it
+ DEFINE_KEYFIELD( m_iszYawPoseParam, FIELD_STRING, "gun_yaw_pose_param" ),
+ DEFINE_KEYFIELD( m_iszPitchPoseParam, FIELD_STRING, "gun_pitch_pose_param" ),
+ DEFINE_KEYFIELD( m_flYawPoseCenter, FIELD_FLOAT, "gun_yaw_pose_center" ),
+ DEFINE_KEYFIELD( m_flPitchPoseCenter, FIELD_FLOAT, "gun_pitch_pose_center" ),
+ DEFINE_FIELD( m_bUsePoseParameters, FIELD_BOOLEAN ),
+
+ DEFINE_KEYFIELD( m_iEffectHandling, FIELD_INTEGER, "effecthandling" ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFireRate", InputSetFireRate ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDamage", InputSetDamage ),
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetPosition", InputSetTargetPosition ),
+ DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetDir", InputSetTargetDir ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetEntityName", InputSetTargetEntityName ),
+ DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetTargetEntity", InputSetTargetEntity ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ClearTargetEntity", InputClearTargetEntity ),
+ DEFINE_INPUTFUNC( FIELD_STRING, "FindNPCToManTank", InputFindNPCToManTank ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StopFindingNPCs", InputStopFindingNPCs ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "StartFindingNPCs", InputStartFindingNPCs ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "ForceNPCOff", InputForceNPCOff ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxRange", InputSetMaxRange ),
+
+ // Outputs
+ DEFINE_OUTPUT(m_OnFire, "OnFire"),
+ DEFINE_OUTPUT(m_OnLoseTarget, "OnLoseTarget"),
+ DEFINE_OUTPUT(m_OnAquireTarget, "OnAquireTarget"),
+ DEFINE_OUTPUT(m_OnAmmoDepleted, "OnAmmoDepleted"),
+ DEFINE_OUTPUT(m_OnGotController, "OnGotController"),
+ DEFINE_OUTPUT(m_OnLostController, "OnLostController"),
+ DEFINE_OUTPUT(m_OnGotPlayerController, "OnGotPlayerController"),
+ DEFINE_OUTPUT(m_OnLostPlayerController, "OnLostPlayerController"),
+ DEFINE_OUTPUT(m_OnReadyToFire, "OnReadyToFire"),
+END_DATADESC()
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFuncTank::CFuncTank()
+{
+ m_nBulletCount = 0;
+
+ m_bNPCInRoute = false;
+ m_flNextControllerSearch = 0;
+ m_bShouldFindNPCs = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CFuncTank::~CFuncTank( void )
+{
+ if ( m_soundLoopRotate != NULL_STRING && ( m_spawnflags & SF_TANK_SOUNDON ) )
+ {
+ StopSound( entindex(), CHAN_STATIC, STRING(m_soundLoopRotate) );
+ }
+}
+
+
+//------------------------------------------------------------------------------
+// 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
+ if ( flTimeDelay <= m_persist2burst )
+ return true;
+
+ // If less than persistence2, occasionally do another burst
+ if ( flTimeDelay <= m_persist2 )
+ {
+ if ( random->RandomInt( 0, 30 ) == 0 )
+ {
+ m_persist2burst = flTimeDelay + 0.5f;
+ 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 = gpGlobals->curtime;
+}
+
+//-----------------------------------------------------------------------------
+// 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 clearing the tank's target entity
+//-----------------------------------------------------------------------------
+void CFuncTank::InputClearTargetEntity( inputdata_t &inputdata )
+{
+ m_targetEntityName = NULL_STRING;
+ m_hTarget = NULL;
+
+ // 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 damage
+//-----------------------------------------------------------------------------
+void CFuncTank::InputSetDamage( inputdata_t &inputdata )
+{
+ m_iBulletDamage = inputdata.value.Int();
+}
+
+//-----------------------------------------------------------------------------
+// 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: Input handler for setting the target as a position.
+//-----------------------------------------------------------------------------
+void CFuncTank::InputSetTargetDir( inputdata_t &inputdata )
+{
+ m_spawnflags |= SF_TANK_AIM_AT_POS;
+ m_hTarget = NULL;
+
+ Vector vecTargetDir;
+ inputdata.value.Vector3D( vecTargetDir );
+ m_vTargetPosition = GetAbsOrigin() + m_barrelPos.LengthSqr() * vecTargetDir;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler for telling the func_tank to find an NPC to man it.
+//-----------------------------------------------------------------------------
+void CFuncTank::InputFindNPCToManTank( inputdata_t &inputdata )
+{
+ // Verify the func_tank is controllable and available.
+ if ( !IsNPCControllable() && !IsNPCSetController() )
+ return;
+
+ // If we have a controller already - don't look for one.
+ if ( HasController() )
+ return;
+
+ // NPC assigned to man the func_tank?
+ CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID() );
+ if ( pEntity )
+ {
+ CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
+ if ( pNPC )
+ {
+ // Verify the npc has the func_tank controller behavior.
+ CAI_FuncTankBehavior *pBehavior;
+ if ( pNPC->GetBehavior( &pBehavior ) )
+ {
+ m_hController = pNPC;
+ pBehavior->SetFuncTank( this );
+ NPC_SetInRoute( true );
+ return;
+ }
+ }
+ }
+
+ // No controller? Find a nearby NPC who can man this func_tank.
+ NPC_FindController();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CFuncTank::InputStopFindingNPCs( inputdata_t &inputdata )
+{
+ m_bShouldFindNPCs = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CFuncTank::InputStartFindingNPCs( inputdata_t &inputdata )
+{
+ m_bShouldFindNPCs = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CFuncTank::InputForceNPCOff( inputdata_t &inputdata )
+{
+ // Interrupt any npc in route (ally or not).
+ if ( NPC_InRoute() )
+ {
+ // Interrupt the npc's route.
+ NPC_InterruptRoute();
+ }
+
+ // If we don't have a controller - then the gun should be free.
+ if ( !m_hController )
+ return;
+
+ CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
+ if ( !pNPC )
+ return;
+
+ CAI_FuncTankBehavior *pBehavior;
+ if ( pNPC->GetBehavior( &pBehavior ) )
+ {
+ pBehavior->Dismount();
+ }
+
+ m_hController = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &inputdata -
+//-----------------------------------------------------------------------------
+void CFuncTank::InputSetMaxRange( inputdata_t &inputdata )
+{
+ m_maxRange = inputdata.value.Float();
+ m_flMaxRange2 = m_maxRange * m_maxRange;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the closest NPC with the func_tank behavior.
+//-----------------------------------------------------------------------------
+void CFuncTank::NPC_FindController( void )
+{
+ // Not NPC controllable or controllable on by specified npc's return.
+ if ( !IsNPCControllable() || IsNPCSetController() )
+ return;
+
+ // Initialize for finding closest NPC.
+ CAI_BaseNPC *pClosestNPC = NULL;
+ float flClosestDist2 = ( FUNCTANK_DISTANCE_MAX * FUNCTANK_DISTANCE_MAX );
+ float flMinDistToEnemy2 = ( FUNCTANK_DISTANCE_MIN_TO_ENEMY * FUNCTANK_DISTANCE_MIN_TO_ENEMY );
+ CAI_FuncTankBehavior *pClosestBehavior = NULL;
+
+ // Get the mount position.
+ Vector vecMountPos;
+ NPC_FindManPoint( vecMountPos );
+
+ // Search through the AI list for the closest NPC with the func_tank behavior.
+ CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
+ int nAICount = g_AI_Manager.NumAIs();
+ for ( int iAI = 0; iAI < nAICount; ++iAI )
+ {
+ CAI_BaseNPC *pNPC = ppAIs[iAI];
+ if ( !pNPC )
+ continue;
+
+ if ( !pNPC->IsAlive() )
+ continue;
+
+ if ( pNPC->IsInAScript() )
+ continue;
+
+ CAI_FuncTankBehavior *pBehavior;
+ if ( pNPC->GetBehavior( &pBehavior ) )
+ {
+ // Don't mount the func_tank if your "enemy" is within X feet or it or the npc.
+ CBaseEntity *pEnemy = pNPC->GetEnemy();
+
+ if ( pEnemy )
+ {
+ if ( !IsEntityInViewCone(pEnemy) )
+ {
+ // Don't mount the tank if the tank can't be aimed at the enemy.
+ continue;
+ }
+
+ float flDist2 = ( pEnemy->GetAbsOrigin() - pNPC->GetAbsOrigin() ).LengthSqr();
+ if ( flDist2 < flMinDistToEnemy2 )
+ continue;
+
+ flDist2 = ( vecMountPos - pEnemy->GetAbsOrigin() ).LengthSqr();
+ if ( flDist2 < flMinDistToEnemy2 )
+ continue;
+
+ if ( !pNPC->FVisible( vecMountPos + pNPC->GetViewOffset() ) )
+ continue;
+ }
+
+ trace_t tr;
+ UTIL_TraceEntity( pNPC, vecMountPos, vecMountPos, MASK_NPCSOLID, this, pNPC->GetCollisionGroup(), &tr );
+ if( tr.startsolid || tr.fraction < 1.0 )
+ {
+ // Don't mount the tank if someone/something is located on the control point.
+ continue;
+ }
+
+ if ( !pBehavior->HasFuncTank() && !pBehavior->IsBusy() )
+ {
+ float flDist2 = ( vecMountPos - pNPC->GetAbsOrigin() ).LengthSqr();
+ if ( flDist2 < flClosestDist2 )
+ {
+ pClosestNPC = pNPC;
+ pClosestBehavior = pBehavior;
+ flClosestDist2 = flDist2;
+ }
+ }
+ }
+ }
+
+ // Set the closest NPC as controller.
+ if ( pClosestNPC )
+ {
+ m_hController = pClosestNPC;
+ pClosestBehavior->SetFuncTank( this );
+ NPC_SetInRoute( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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: Override base class to add display of fly direction
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+void CFuncTank::DrawDebugGeometryOverlays(void)
+{
+ // Center
+ QAngle angCenter;
+ Vector vecForward;
+ angCenter = QAngle( 0, YawCenterWorld(), 0 );
+ AngleVectors( angCenter, &vecForward );
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 64), 255,255,255, true, 0.1);
+
+ // Draw the yaw ranges
+ angCenter = QAngle( 0, YawCenterWorld() + m_yawRange, 0 );
+ AngleVectors( angCenter, &vecForward );
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 0,255,0, true, 0.1);
+ angCenter = QAngle( 0, YawCenterWorld() - m_yawRange, 0 );
+ AngleVectors( angCenter, &vecForward );
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 0,255,0, true, 0.1);
+
+ // Draw the pitch ranges
+ angCenter = QAngle( PitchCenterWorld() + m_pitchRange, 0, 0 );
+ AngleVectors( angCenter, &vecForward );
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 255,0,0, true, 0.1);
+ angCenter = QAngle( PitchCenterWorld() - m_pitchRange, 0, 0 );
+ AngleVectors( angCenter, &vecForward );
+ NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 255,0,0, true, 0.1);
+
+ BaseClass::DrawDebugGeometryOverlays();
+}
+
+//-----------------------------------------------------------------------------
+// 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);
+
+ 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);
+ return true;
+ }
+
+ if (FStrEq(szKeyName, "barrely"))
+ {
+ m_barrelPos.y = atof(szValue);
+ return true;
+ }
+
+ if (FStrEq(szKeyName, "barrelz"))
+ {
+ m_barrelPos.z = atof(szValue);
+ return true;
+ }
+
+ return BaseClass::KeyValue( szKeyName, szValue );
+}
+
+
+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)
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::Spawn( void )
+{
+ Precache();
+
+#ifdef HL2_EPISODIC
+ m_iAmmoType = GetAmmoDef()->Index( STRING( m_iszAmmoType ) );
+#else
+ m_iSmallAmmoType = GetAmmoDef()->Index("Pistol");
+ m_iMediumAmmoType = GetAmmoDef()->Index("SMG1");
+ m_iLargeAmmoType = GetAmmoDef()->Index("AR2");
+#endif // HL2_EPISODIC
+
+ SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything
+ SetSolid( SOLID_VPHYSICS );
+ SetModel( STRING( GetModelName() ) );
+ AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
+
+ if ( HasSpawnFlags(SF_TANK_NOTSOLID) )
+ {
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ }
+
+ m_hControlVolume = NULL;
+
+ if ( GetParent() && GetParent()->GetBaseAnimating() )
+ {
+ CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
+ if ( m_iszBaseAttachment != NULL_STRING )
+ {
+ int nAttachment = pAnim->LookupAttachment( STRING( m_iszBaseAttachment ) );
+ if ( nAttachment != 0 )
+ {
+ SetParent( pAnim, nAttachment );
+ SetLocalOrigin( vec3_origin );
+ SetLocalAngles( vec3_angle );
+ }
+ }
+
+ m_bUsePoseParameters = (m_iszYawPoseParam != NULL_STRING) && (m_iszPitchPoseParam != NULL_STRING);
+
+ if ( m_iszBarrelAttachment != NULL_STRING )
+ {
+ if ( m_bUsePoseParameters )
+ {
+ pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), 0 );
+ pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), 0 );
+ pAnim->InvalidateBoneCache();
+ }
+
+ m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) );
+
+ Vector vecWorldBarrelPos;
+ QAngle worldBarrelAngle;
+ pAnim->GetAttachment( m_nBarrelAttachment, vecWorldBarrelPos, worldBarrelAngle );
+ VectorITransform( vecWorldBarrelPos, EntityToWorldTransform( ), m_barrelPos );
+ }
+
+ if ( m_bUsePoseParameters )
+ {
+ // In this case, we're relying on the parent to have the gun model
+ AddEffects( EF_NODRAW );
+ QAngle localAngles( m_flPitchPoseCenter, m_flYawPoseCenter, 0 );
+ SetLocalAngles( localAngles );
+ SetSolid( SOLID_NONE );
+ SetMoveType( MOVETYPE_NOCLIP );
+
+ // If our parent is a prop_dynamic, make it use hitboxes for renderbox
+ CDynamicProp *pProp = dynamic_cast<CDynamicProp*>(GetParent());
+ if ( pProp )
+ {
+ pProp->m_bUseHitboxesForRenderBox = true;
+ }
+ }
+ }
+
+ // For smoothing out leading
+ m_flStartLeadFactor = 1.0f;
+ m_flNextLeadFactor = 1.0f;
+ m_flStartLeadFactorTime = gpGlobals->curtime;
+ m_flNextLeadFactorTime = gpGlobals->curtime + 1.0f;
+
+ m_yawCenter = GetLocalAngles().y;
+ m_yawCenterWorld = GetAbsAngles().y;
+ m_pitchCenter = GetLocalAngles().x;
+ m_pitchCenterWorld = GetAbsAngles().y;
+ m_vTargetPosition = vec3_origin;
+
+ if ( IsActive() || (IsControllable() && !HasController()) )
+ {
+ // Think to find controllers.
+ SetNextThink( gpGlobals->curtime + 1.0f );
+ m_flNextControllerSearch = 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;
+ if ( GetSolid() != SOLID_NONE )
+ {
+ CreateVPhysics();
+ }
+
+ // Setup squared min/max range.
+ m_flMinRange2 = m_minRange * m_minRange;
+ m_flMaxRange2 = m_maxRange * m_maxRange;
+ m_flIgnoreGraceUpto *= m_flIgnoreGraceUpto;
+
+ m_flLastSawNonPlayer = 0;
+
+ if( IsActive() )
+ {
+ m_OnReadyToFire.FireOutput( this, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::Activate( void )
+{
+ BaseClass::Activate();
+
+ // Necessary for save/load
+ if ( (m_iszBarrelAttachment != NULL_STRING) && (m_nBarrelAttachment == 0) )
+ {
+ if ( GetParent() && GetParent()->GetBaseAnimating() )
+ {
+ CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
+ m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) );
+ }
+ }
+}
+
+bool CFuncTank::CreateVPhysics()
+{
+ VPhysicsInitShadow( false, false );
+ return true;
+}
+
+
+void CFuncTank::Precache( void )
+{
+ 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) );
+
+ PrecacheScriptSound( "Func_Tank.BeginUse" );
+
+ // Precache the combine cannon
+ if ( m_iEffectHandling == EH_COMBINE_CANNON )
+ {
+ PrecacheScriptSound( "NPC_Combine_Cannon.FireBullet" );
+ }
+}
+
+void CFuncTank::UpdateOnRemove( void )
+{
+ if ( HasController() )
+ {
+ StopControl();
+ }
+ BaseClass::UpdateOnRemove();
+}
+
+
+//-----------------------------------------------------------------------------
+// Barrel position
+//-----------------------------------------------------------------------------
+void CFuncTank::UpdateMatrix( void )
+{
+ m_parentMatrix.InitFromEntity( GetParent(), GetParentAttachment() );
+}
+
+
+//-----------------------------------------------------------------------------
+// Barrel position
+//-----------------------------------------------------------------------------
+Vector CFuncTank::WorldBarrelPosition( void )
+{
+ if ( (m_nBarrelAttachment == 0) || !GetParent() )
+ {
+ EntityMatrix tmp;
+ tmp.InitFromEntity( this );
+ return tmp.LocalToWorld( m_barrelPos );
+ }
+
+ Vector vecOrigin;
+ QAngle vecAngles;
+ CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
+ pAnim->GetAttachment( m_nBarrelAttachment, vecOrigin, vecAngles );
+ return vecOrigin;
+}
+
+
+//-----------------------------------------------------------------------------
+// Make the parent's pose parameters match the func_tank
+//-----------------------------------------------------------------------------
+void CFuncTank::PhysicsSimulate( void )
+{
+ BaseClass::PhysicsSimulate();
+
+ if ( m_bUsePoseParameters && GetParent() )
+ {
+ const QAngle &angles = GetLocalAngles();
+ CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
+ pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), angles.y );
+ pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), angles.x );
+ pAnim->StudioFrameAdvance();
+ }
+}
+
+//=============================================================================
+//
+// TANK CONTROLLING
+//
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncTank::OnControls( CBaseEntity *pTest )
+{
+ // Is the tank controllable.
+ if ( !IsControllable() )
+ return false;
+
+ if ( !m_hControlVolume )
+ {
+ // Find our control volume
+ if ( m_iszControlVolume != NULL_STRING )
+ {
+ m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) );
+ }
+
+ if (( !m_hControlVolume ) && IsControllable() )
+ {
+ Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) );
+ return false;
+ }
+ }
+
+ if ( m_hControlVolume->IsTouching( pTest ) )
+ return true;
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncTank::StartControl( CBaseCombatCharacter *pController )
+{
+ // Check to see if we have a controller.
+ if ( HasController() && GetController() != pController )
+ return false;
+
+ // Team only or disabled?
+ if ( m_iszMaster != NULL_STRING )
+ {
+ if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) )
+ return false;
+ }
+
+ // Set func_tank as manned by player/npc.
+ m_hController = pController;
+ if ( pController->IsPlayer() )
+ {
+ m_spawnflags |= SF_TANK_PLAYER;
+
+ CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() );
+ pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION;
+ }
+ else
+ {
+ m_spawnflags |= SF_TANK_NPC;
+ NPC_SetInRoute( false );
+ }
+
+ // Holster player/npc weapon
+ if ( m_hController->GetActiveWeapon() )
+ {
+ m_hController->GetActiveWeapon()->Holster();
+ }
+
+ // Set the controller's position to be the use position.
+ m_vecControllerUsePos = m_hController->GetLocalOrigin();
+
+ EmitSound( "Func_Tank.BeginUse" );
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ // Let the map maker know a controller has been found
+ if ( m_hController->IsPlayer() )
+ {
+ m_OnGotPlayerController.FireOutput( this, this );
+ }
+ else
+ {
+ m_OnGotController.FireOutput( this, this );
+ }
+
+ OnStartControlled();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// TODO: bring back the controllers current weapon
+//-----------------------------------------------------------------------------
+void CFuncTank::StopControl()
+{
+ // Do we have a controller?
+ if ( !m_hController )
+ return;
+
+ OnStopControlled();
+
+ // Arm player/npc weapon.
+ if ( m_hController->GetActiveWeapon() )
+ {
+ m_hController->GetActiveWeapon()->Deploy();
+ }
+
+ if ( m_hController->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() );
+ pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION;
+ }
+
+ // Stop thinking.
+ SetNextThink( TICK_NEVER_THINK );
+
+ // Let the map maker know a controller has been lost.
+ if ( m_hController->IsPlayer() )
+ {
+ m_OnLostPlayerController.FireOutput( this, this );
+ }
+ else
+ {
+ m_OnLostController.FireOutput( this, this );
+ }
+
+ // Reset the func_tank as unmanned (player/npc).
+ if ( m_hController->IsPlayer() )
+ {
+ m_spawnflags &= ~SF_TANK_PLAYER;
+ }
+ else
+ {
+ m_spawnflags &= ~SF_TANK_NPC;
+ }
+
+ m_hController = NULL;
+
+ // Set think, if the func_tank can think on its own.
+ if ( IsActive() || (IsControllable() && !HasController()) )
+ {
+ // Delay the think to find controllers a bit
+#ifdef FUNCTANK_AUTOUSE
+ m_flNextControllerSearch = gpGlobals->curtime + 1.0f;
+#else
+ m_flNextControllerSearch = gpGlobals->curtime + 5.0f;
+#endif//FUNCTANK_AUTOUSE
+
+ SetNextThink( m_flNextControllerSearch );
+ }
+
+ SetLocalAngularVelocity( vec3_angle );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Called each frame by the player's ItemPostFrame
+//-----------------------------------------------------------------------------
+
+// NVNT turret recoil
+ConVar hap_turret_mag("hap_turret_mag", "5", 0);
+
+void CFuncTank::ControllerPostFrame( void )
+{
+ // Make sure we have a contoller.
+ Assert( m_hController != NULL );
+
+ // Control the firing rate.
+ if ( gpGlobals->curtime < m_flNextAttack )
+ return;
+
+ if ( !IsPlayerManned() )
+ return;
+
+ CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() );
+ if ( ( pPlayer->m_nButtons & IN_ATTACK ) == 0 )
+ return;
+
+ Vector forward;
+ AngleVectors( GetAbsAngles(), &forward );
+ 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;
+
+ if( HasSpawnFlags( SF_TANK_AIM_ASSISTANCE ) )
+ {
+ // Trace out a hull and if it hits something, adjust the shot to hit that thing.
+ trace_t tr;
+ Vector start = WorldBarrelPosition();
+ Vector dir = forward;
+
+ UTIL_TraceHull( start, start + forward * 8192, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO && (tr.m_pEnt->GetFlags() & FL_AIMTARGET) )
+ {
+ forward = tr.m_pEnt->WorldSpaceCenter() - start;
+ VectorNormalize( forward );
+ }
+ }
+
+ Fire( bulletCount, WorldBarrelPosition(), forward, pPlayer, false );
+
+#if defined( WIN32 ) && !defined( _X360 )
+ // NVNT apply a punch on the player each time fired
+ HapticPunch(pPlayer,0,0,hap_turret_mag.GetFloat());
+#endif
+ // HACKHACK -- make some noise (that the AI can hear)
+ CSoundEnt::InsertSound( SOUND_COMBAT, WorldSpaceCenter(), FUNCTANK_FIREVOLUME, 0.2 );
+
+ if( m_iAmmoCount > -1 )
+ {
+ if( !(m_iAmmoCount % 10) )
+ {
+ Msg("Ammo Remaining: %d\n", m_iAmmoCount );
+ }
+
+ if( --m_iAmmoCount == 0 )
+ {
+ // Kick the player off the gun, and make myself not usable.
+ m_spawnflags &= ~SF_TANK_CANCONTROL;
+ StopControl();
+ return;
+ }
+ }
+
+ SetNextAttack( gpGlobals->curtime + (1/m_fireRate) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CFuncTank::HasController( void )
+{
+ return (m_hController != NULL);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : CBaseCombatCharacter
+//-----------------------------------------------------------------------------
+CBaseCombatCharacter *CFuncTank::GetController( void )
+{
+ return m_hController;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncTank::NPC_FindManPoint( Vector &vecPos )
+{
+ if ( m_iszNPCManPoint != NULL_STRING )
+ {
+ CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_iszNPCManPoint );
+ if ( pEntity )
+ {
+ vecPos = pEntity->GetAbsOrigin();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The NPC manning this gun just saw a player for the first time since he left cover
+//-----------------------------------------------------------------------------
+void CFuncTank::NPC_JustSawPlayer( CBaseEntity *pTarget )
+{
+ SetNextAttack( gpGlobals->curtime + m_flPlayerLockTimeBeforeFire );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::NPC_Fire( void )
+{
+ // Control the firing rate.
+ if ( gpGlobals->curtime < m_flNextAttack )
+ return;
+
+ // Check for a valid npc controller.
+ if ( !m_hController )
+ return;
+
+ CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
+ if ( !pNPC )
+ return;
+
+ // Setup for next round of firing.
+ if ( m_nBulletCount == 0 )
+ {
+ m_nBulletCount = GetRandomBurst();
+ m_fireTime = 1.0f;
+ }
+
+ // m_fireLast looks like it is only needed for Active non-controlled func_tank.
+// m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets
+
+ Vector vecBarrelEnd = WorldBarrelPosition();
+ Vector vecForward;
+ AngleVectors( GetAbsAngles(), &vecForward );
+
+ if ( (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && pNPC->IsInSquad() )
+ {
+ // Avoid shooting squadmates.
+ if ( pNPC->IsSquadmateInSpread( vecBarrelEnd, vecBarrelEnd + vecForward * 2048, gTankSpread[m_spread].x, 8*12 ) )
+ {
+ return;
+ }
+ }
+
+ if ( !HasSpawnFlags( SF_TANK_ALLOW_PLAYER_HITS ) && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) )
+ {
+ // Avoid shooting player.
+ if ( pNPC->PlayerInSpread( vecBarrelEnd, vecBarrelEnd + vecForward * 2048, gTankSpread[m_spread].x, 8*12 ) )
+ {
+ return;
+ }
+ }
+
+ bool bIgnoreSpread = false;
+
+ CBaseEntity *pEnemy = pNPC->GetEnemy();
+ if ( HasSpawnFlags( SF_TANK_HACKPLAYERHIT ) && pEnemy && pEnemy->IsPlayer() )
+ {
+ // Every third shot should be fired directly at the player
+ if ( m_nBulletCount%2 == 0 )
+ {
+ Vector vecBodyTarget = pEnemy->BodyTarget( vecBarrelEnd, false );
+ vecForward = (vecBodyTarget - vecBarrelEnd);
+ VectorNormalize( vecForward );
+ bIgnoreSpread = true;
+ }
+ }
+
+ // Fire the bullet(s).
+ Fire( 1, vecBarrelEnd, vecForward, m_hController, bIgnoreSpread );
+ --m_nBulletCount;
+
+ // Check ammo counts and dismount when empty.
+ if( m_iAmmoCount > -1 )
+ {
+ if( --m_iAmmoCount == 0 )
+ {
+ // Disable the func_tank.
+ m_spawnflags &= ~SF_TANK_CANCONTROL;
+
+ // Remove the npc.
+ StopControl();
+ return;
+ }
+ }
+
+ float flFireTime = GetRandomFireTime();
+ if ( m_nBulletCount != 0 )
+ {
+ m_fireTime -= flFireTime;
+ SetNextAttack( gpGlobals->curtime + flFireTime );
+ }
+ else
+ {
+ SetNextAttack( gpGlobals->curtime + m_fireTime );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncTank::NPC_HasEnemy( void )
+{
+ if ( !IsNPCManned() )
+ return false;
+
+ CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
+ Assert( pNPC );
+
+ return ( pNPC->GetEnemy() != NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::NPC_InterruptRoute( void )
+{
+ if ( !m_hController )
+ return;
+
+ CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
+ if ( !pNPC )
+ return;
+
+ CAI_FuncTankBehavior *pBehavior;
+ if ( pNPC->GetBehavior( &pBehavior ) )
+ {
+ pBehavior->SetFuncTank( NULL );
+ }
+
+ // Reset the npc controller.
+ m_hController = NULL;
+
+ // No NPC's in route.
+ NPC_SetInRoute( false );
+
+ // Delay the think to find controllers a bit
+ m_flNextControllerSearch = gpGlobals->curtime + 5.0f;
+
+ if ( !HasController() )
+ {
+ // Start thinking to find controllers again
+ SetNextThink( m_flNextControllerSearch );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncTank::NPC_InterruptController( void )
+{
+ // If we don't have a controller - then the gun should be free.
+ if ( !m_hController )
+ return true;
+
+ CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
+ if ( !pNPC || !pNPC->IsPlayerAlly() )
+ return false;
+
+ CAI_FuncTankBehavior *pBehavior;
+ if ( pNPC->GetBehavior( &pBehavior ) )
+ {
+ pBehavior->Dismount();
+ }
+
+ m_hController = NULL;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : float
+//-----------------------------------------------------------------------------
+float CFuncTank::GetRandomFireTime( void )
+{
+ Assert( m_fireRate != 0 );
+ float flOOFireRate = 1.0f / m_fireRate;
+ float flOOFireRateBy2 = flOOFireRate * 0.5f;
+ float flOOFireRateBy4 = flOOFireRate * 0.25f;
+ return random->RandomFloat( flOOFireRateBy4, flOOFireRateBy2 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : int
+//-----------------------------------------------------------------------------
+int CFuncTank::GetRandomBurst( void )
+{
+ return random->RandomInt( m_fireRate-2, m_fireRate+2 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pActivator -
+// *pCaller -
+// useType -
+// value -
+//-----------------------------------------------------------------------------
+void CFuncTank::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
+{
+ if ( !IsControllable() )
+ return;
+
+ // player controlled turret
+ CBasePlayer *pPlayer = ToBasePlayer( pActivator );
+ if ( !pPlayer )
+ return;
+
+ if ( value == 2 && useType == USE_SET )
+ {
+ ControllerPostFrame();
+ }
+ else if ( m_hController != pPlayer && useType != USE_OFF )
+ {
+ // The player must be within the func_tank controls
+ if ( !m_hControlVolume )
+ {
+ // Find our control volume
+ if ( m_iszControlVolume != NULL_STRING )
+ {
+ m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) );
+ }
+
+ if (( !m_hControlVolume ) && IsControllable() )
+ {
+ Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) );
+ return;
+ }
+ }
+
+ if ( !m_hControlVolume->IsTouching( pPlayer ) )
+ return;
+
+ // Interrupt any npc in route (ally or not).
+ if ( NPC_InRoute() )
+ {
+ // Interrupt the npc's route.
+ NPC_InterruptRoute();
+ }
+
+ // Interrupt NPC - if possible (they must be allies).
+ if ( IsNPCControllable() && HasController() )
+ {
+ if ( !NPC_InterruptController() )
+ return;
+ }
+
+ pPlayer->SetUseEntity( this );
+ StartControl( pPlayer );
+ }
+ else
+ {
+ StopControl();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : range -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CFuncTank::InRange( float range )
+{
+ if ( range < m_minRange )
+ return FALSE;
+ if ( (m_maxRange > 0) && (range > m_maxRange) )
+ return FALSE;
+
+ return TRUE;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncTank::InRange2( float flRange2 )
+{
+ if ( flRange2 < m_flMinRange2 )
+ return false;
+
+ if ( ( m_flMaxRange2 > 0.0f ) && ( flRange2 > m_flMaxRange2 ) )
+ return false;
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::Think( void )
+{
+ FuncTankPreThink();
+
+ m_hFuncTankTarget = NULL;
+
+ // Look for a new controller?
+ if ( IsControllable() && !HasController() && (m_flNextControllerSearch <= gpGlobals->curtime) )
+ {
+ if ( m_bShouldFindNPCs && gpGlobals->curtime > 5.0f )
+ {
+ // Check for in route and timer.
+ if ( !NPC_InRoute() )
+ {
+ NPC_FindController();
+ }
+ }
+
+#ifdef FUNCTANK_AUTOUSE
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
+ bool bThinkFast = false;
+
+ if( pPlayer )
+ {
+ if ( !m_hControlVolume )
+ {
+ // Find our control volume
+ if ( m_iszControlVolume != NULL_STRING )
+ {
+ m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) );
+ }
+
+ if (( !m_hControlVolume ) && IsControllable() )
+ {
+ Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) );
+ return;
+ }
+ }
+
+ if ( m_hControlVolume )
+ {
+ if( m_hControlVolume->IsTouching( pPlayer ) && pPlayer->FInViewCone(WorldSpaceCenter()) )
+ {
+ // If my control volume is touching a player that's facing the mounted gun, automatically use the gun.
+ // !!!BUGBUG - this only works in cases where the player can see the gun whilst standing in the control
+ // volume. (This works just fine for all func_tanks mounted on combine walls and small barriers)
+ variant_t emptyVariant;
+ AcceptInput( "Use", pPlayer, pPlayer, emptyVariant, USE_TOGGLE );
+ }
+ else
+ {
+ // If the player is nearby, think faster for snappier response to XBox auto mounting
+ float flDistSqr = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() );
+
+ if( flDistSqr <= Square(360) )
+ {
+ bThinkFast = true;
+ }
+ }
+ }
+ }
+
+ // Keep thinking, in case they turn NPC finding back on
+ if ( !HasController() )
+ {
+ if( bThinkFast )
+ {
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 2.0f );
+ }
+ }
+
+ if( bThinkFast )
+ {
+ m_flNextControllerSearch = gpGlobals->curtime + 0.1f;
+ }
+ else
+ {
+ m_flNextControllerSearch = gpGlobals->curtime + 2.0f;
+ }
+#else
+ // Keep thinking, in case they turn NPC finding back on
+ if ( !HasController() )
+ {
+ SetNextThink( gpGlobals->curtime + 2.0f );
+ }
+
+ m_flNextControllerSearch = gpGlobals->curtime + 2.0f;
+#endif//FUNCTANK_AUTOUSE
+ }
+
+ // refresh the matrix
+ UpdateMatrix();
+
+ SetLocalAngularVelocity( vec3_angle );
+ TrackTarget();
+
+ if ( fabs(GetLocalAngularVelocity().x) > 1 || fabs(GetLocalAngularVelocity().y) > 1 )
+ {
+ StartRotSound();
+ }
+ else
+ {
+ StopRotSound();
+ }
+
+ FuncTankPostThink();
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Aim the tank at the player crosshair
+//-----------------------------------------------------------------------------
+void CFuncTank::CalcPlayerCrosshairTarget( Vector *pVecTarget )
+{
+ // Get the player.
+ CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() );
+
+ // Tank aims at player's crosshair.
+ Vector vecStart, vecDir;
+ trace_t tr;
+
+ vecStart = pPlayer->EyePosition();
+
+ if ( !IsX360() )
+ {
+ vecDir = pPlayer->EyeDirection3D();
+ }
+ else
+ {
+ // Use autoaim as the eye dir.
+ vecDir = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT );
+ }
+
+ // Make sure to start the trace outside of the player's bbox!
+ UTIL_TraceLine( vecStart + vecDir * 24, vecStart + vecDir * 8192, MASK_BLOCKLOS_AND_NPCS, this, COLLISION_GROUP_NONE, &tr );
+
+ *pVecTarget = tr.endpos;
+}
+
+//-----------------------------------------------------------------------------
+// Aim the tank at the player crosshair
+//-----------------------------------------------------------------------------
+void CFuncTank::AimBarrelAtPlayerCrosshair( QAngle *pAngles )
+{
+ Vector vecTarget;
+ CalcPlayerCrosshairTarget( &vecTarget );
+ *pAngles = AimBarrelAt( m_parentMatrix.WorldToLocal( vecTarget ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Aim the tank at the NPC's enemy
+//-----------------------------------------------------------------------------
+void CFuncTank::CalcNPCEnemyTarget( Vector *pVecTarget )
+{
+ Vector vecTarget;
+ CAI_BaseNPC *pNPC = m_hController->MyNPCPointer();
+
+ // Aim the barrel at the npc's enemy, or where the npc is looking.
+ CBaseEntity *pEnemy = pNPC->GetEnemy();
+ if ( pEnemy )
+ {
+ // Clear the idle target
+ *pVecTarget = pEnemy->BodyTarget( GetAbsOrigin(), false );
+ m_vecNPCIdleTarget = *pVecTarget;
+ }
+ else
+ {
+ if ( m_vecNPCIdleTarget != vec3_origin )
+ {
+ *pVecTarget = m_vecNPCIdleTarget;
+ }
+ else
+ {
+ Vector vecForward;
+ QAngle angCenter( 0, m_yawCenterWorld, 0 );
+ AngleVectors( angCenter, &vecForward );
+ trace_t tr;
+ Vector vecBarrel = GetAbsOrigin() + m_barrelPos;
+ UTIL_TraceLine( vecBarrel, vecBarrel + vecForward * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ *pVecTarget = tr.endpos;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Aim the tank at the NPC's enemy
+//-----------------------------------------------------------------------------
+void CFuncTank::AimBarrelAtNPCEnemy( QAngle *pAngles )
+{
+ Vector vecTarget;
+ CalcNPCEnemyTarget( &vecTarget );
+ *pAngles = AimBarrelAt( m_parentMatrix.WorldToLocal( vecTarget ) );
+}
+
+//-----------------------------------------------------------------------------
+// Returns true if the desired angles are out of range
+//-----------------------------------------------------------------------------
+bool CFuncTank::RotateTankToAngles( const QAngle &angles, float *pDistX, float *pDistY )
+{
+ bool bClamped = false;
+
+ // 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 );
+
+ float flActualYaw = m_yawCenter + offsetY;
+ float flActualPitch = m_pitchCenter + offsetX;
+
+ if ( ( fabs( offsetY ) > m_yawRange + m_yawTolerance ) ||
+ ( fabs( offsetX ) > m_pitchRange + m_pitchTolerance ) )
+ {
+ // Limit against range in x
+ flActualYaw = clamp( flActualYaw, m_yawCenter - m_yawRange, m_yawCenter + m_yawRange );
+ flActualPitch = clamp( flActualPitch, m_pitchCenter - m_pitchRange, m_pitchCenter + m_pitchRange );
+
+ bClamped = true;
+ }
+
+ // Get at the angular vel
+ QAngle vecAngVel = GetLocalAngularVelocity();
+
+ // Move toward target at rate or less
+ float distY = UTIL_AngleDistance( flActualYaw, GetLocalAngles().y );
+ vecAngVel.y = distY * 10;
+ vecAngVel.y = clamp( vecAngVel.y, -m_yawRate, m_yawRate );
+
+ // Move toward target at rate or less
+ float distX = UTIL_AngleDistance( flActualPitch, GetLocalAngles().x );
+ vecAngVel.x = distX * 10;
+ vecAngVel.x = clamp( vecAngVel.x, -m_pitchRate, m_pitchRate );
+
+ // How exciting! We're done
+ SetLocalAngularVelocity( vecAngVel );
+
+ if ( pDistX && pDistY )
+ {
+ *pDistX = distX;
+ *pDistY = distY;
+ }
+
+ return bClamped;
+}
+
+
+//-----------------------------------------------------------------------------
+// We lost our target!
+//-----------------------------------------------------------------------------
+void CFuncTank::LostTarget( void )
+{
+ if (m_fireLast != 0)
+ {
+ m_OnLoseTarget.FireOutput(this, this);
+ m_fireLast = 0;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition )
+{
+ Vector vecTarget = pTarget->BodyTarget( vecShootPosition, false );
+ float flShotSpeed = GetShotSpeed();
+ if ( flShotSpeed == 0 )
+ {
+ *pLeadPosition = vecTarget;
+ return;
+ }
+
+ Vector vecVelocity = pTarget->GetSmoothedVelocity();
+ vecVelocity.z = 0.0f;
+ float flTargetSpeed = VectorNormalize( vecVelocity );
+
+ // Guesstimate...
+ if ( m_flNextLeadFactorTime < gpGlobals->curtime )
+ {
+ m_flStartLeadFactor = m_flNextLeadFactor;
+ m_flStartLeadFactorTime = gpGlobals->curtime;
+ m_flNextLeadFactor = random->RandomFloat( 0.8f, 1.3f );
+ m_flNextLeadFactorTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f );
+ }
+
+ float flFactor = (gpGlobals->curtime - m_flStartLeadFactorTime) / (m_flNextLeadFactorTime - m_flStartLeadFactorTime);
+ float flLeadFactor = SimpleSplineRemapVal( flFactor, 0.0f, 1.0f, m_flStartLeadFactor, m_flNextLeadFactor );
+ flTargetSpeed *= flLeadFactor;
+
+ Vector vecDelta;
+ VectorSubtract( vecShootPosition, vecTarget, vecDelta );
+ float flTargetToShooter = VectorNormalize( vecDelta );
+ float flCosTheta = DotProduct( vecDelta, vecVelocity );
+
+ // Law of cosines... z^2 = x^2 + y^2 - 2xy cos Theta
+ // where z = flShooterToPredictedTargetPosition = flShotSpeed * predicted time
+ // x = flTargetSpeed * predicted time
+ // y = flTargetToShooter
+ // solve for predicted time using at^2 + bt + c = 0, t = (-b +/- sqrt( b^2 - 4ac )) / 2a
+ float a = flTargetSpeed * flTargetSpeed - flShotSpeed * flShotSpeed;
+ float b = -2.0f * flTargetToShooter * flCosTheta * flTargetSpeed;
+ float c = flTargetToShooter * flTargetToShooter;
+
+ float flDiscrim = b*b - 4*a*c;
+ if (flDiscrim < 0)
+ {
+ *pLeadPosition = vecTarget;
+ return;
+ }
+
+ flDiscrim = sqrt(flDiscrim);
+ float t = (-b + flDiscrim) / (2.0f * a);
+ float t2 = (-b - flDiscrim) / (2.0f * a);
+ if ( t < t2 )
+ {
+ t = t2;
+ }
+
+ if ( t <= 0.0f )
+ {
+ *pLeadPosition = vecTarget;
+ return;
+ }
+
+ VectorMA( vecTarget, flTargetSpeed * t, vecVelocity, *pLeadPosition );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::AimFuncTankAtTarget( void )
+{
+ // Get world target position
+ CBaseEntity *pTarget = NULL;
+ trace_t tr;
+ QAngle angles;
+ bool bUpdateTime = false;
+
+ CBaseEntity *pTargetVehicle = NULL;
+ Vector 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 )
+ {
+ m_hTarget = FindTarget( m_targetEntityName, NULL );
+ }
+
+ LostTarget();
+ return;
+ }
+
+ pTarget = pEntity;
+
+ // Calculate angle needed to aim at target
+ worldTargetPosition = pEntity->EyePosition();
+ if ( pEntity->IsPlayer() )
+ {
+ CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(pEntity);
+ pTargetVehicle = pPlayer->GetVehicleEntity();
+ if ( pTargetVehicle )
+ {
+ worldTargetPosition = pTargetVehicle->BodyTarget( GetAbsOrigin(), false );
+ }
+ }
+ }
+
+ float range2 = worldTargetPosition.DistToSqr( barrelEnd );
+ if ( !InRange2( range2 ) )
+ {
+ if ( m_hTarget )
+ {
+ m_hTarget = NULL;
+ LostTarget();
+ }
+ return;
+ }
+
+ Vector vecAimOrigin = m_sightOrigin;
+ if (m_spawnflags & SF_TANK_AIM_AT_POS)
+ {
+ bUpdateTime = true;
+ m_sightOrigin = m_vTargetPosition;
+ vecAimOrigin = m_sightOrigin;
+ }
+ else
+ {
+ if ( m_spawnflags & SF_TANK_LINEOFSIGHT )
+ {
+ AI_TraceLOS( barrelEnd, worldTargetPosition, this, &tr );
+ }
+ else
+ {
+ tr.fraction = 1.0f;
+ tr.m_pEnt = pTarget;
+ }
+
+ // No line of sight, don't track
+ if ( tr.fraction == 1.0 || tr.m_pEnt == pTarget || (pTargetVehicle && (tr.m_pEnt == pTargetVehicle)) )
+ {
+ if ( InRange2( range2 ) && pTarget && pTarget->IsAlive() )
+ {
+ bUpdateTime = true;
+
+ // Sight position is BodyTarget with no noise (so gun doesn't bob up and down)
+ CBaseEntity *pInstance = pTargetVehicle ? pTargetVehicle : pTarget;
+ m_hFuncTankTarget = pInstance;
+
+ m_sightOrigin = pInstance->BodyTarget( GetAbsOrigin(), false );
+ if ( m_bPerformLeading )
+ {
+ ComputeLeadingPosition( barrelEnd, pInstance, &vecAimOrigin );
+ }
+ else
+ {
+ vecAimOrigin = m_sightOrigin;
+ }
+ }
+ }
+ }
+
+ // Convert targetPosition to parent
+ Vector vecLocalOrigin = m_parentMatrix.WorldToLocal( vecAimOrigin );
+ angles = AimBarrelAt( vecLocalOrigin );
+
+ // FIXME: These need to be the clamped angles
+ float distX, distY;
+ bool bClamped = RotateTankToAngles( angles, &distX, &distY );
+ if ( bClamped )
+ {
+ bUpdateTime = false;
+ }
+
+ if ( bUpdateTime )
+ {
+ if( (gpGlobals->curtime - m_lastSightTime >= 1.0) && (gpGlobals->curtime > m_flNextAttack) )
+ {
+ // Enemy was hidden for a while, and I COULD fire right now. Instead, tack a delay on.
+ m_flNextAttack = gpGlobals->curtime + 0.5;
+ }
+
+ m_lastSightTime = gpGlobals->curtime;
+ m_persist2burst = 0;
+ }
+
+ SetMoveDoneTime( 0.1 );
+
+ 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 )
+ {
+ AI_TraceLine( barrelEnd, pTarget->WorldSpaceCenter(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction == 1.0f || (tr.m_pEnt && 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
+ {
+ LostTarget();
+ }
+ }
+ else
+ {
+ LostTarget();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::TrackTarget( void )
+{
+ QAngle angles;
+
+ if( !m_bReadyToFire && m_flNextAttack <= gpGlobals->curtime )
+ {
+ m_OnReadyToFire.FireOutput( this, this );
+ m_bReadyToFire = true;
+ }
+
+ if ( IsPlayerManned() )
+ {
+ AimBarrelAtPlayerCrosshair( &angles );
+ RotateTankToAngles( angles );
+ SetNextThink( gpGlobals->curtime + 0.05f );
+ SetMoveDoneTime( 0.1 );
+ return;
+ }
+
+ if ( IsNPCManned() )
+ {
+ AimBarrelAtNPCEnemy( &angles );
+ RotateTankToAngles( angles );
+ SetNextThink( gpGlobals->curtime + 0.05f );
+ SetMoveDoneTime( 0.1 );
+ return;
+ }
+
+ if ( !IsActive() )
+ {
+ // If we're not active, but we're controllable, we need to keep thinking
+ if ( IsControllable() && !HasController() )
+ {
+ // Think to find controllers.
+ SetNextThink( m_flNextControllerSearch );
+ }
+ return;
+ }
+
+ // Clean room for unnecessarily complicated old code
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ AimFuncTankAtTarget();
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 )
+ {
+ // NOTE: Set m_fireLast first so that Fire can adjust it
+ m_fireLast = gpGlobals->curtime;
+ Fire( bulletCount, barrelEnd, forward, pAttacker, false );
+ }
+ }
+ else
+ {
+ m_fireLast = gpGlobals->curtime;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTank::DoMuzzleFlash( void )
+{
+ // If we're parented to something, make it play the muzzleflash
+ if ( m_bUsePoseParameters && GetParent() )
+ {
+ CBaseAnimating *pAnim = GetParent()->GetBaseAnimating();
+ pAnim->DoMuzzleFlash();
+
+ // Do the AR2 muzzle flash
+ if ( m_iEffectHandling == EH_COMBINE_CANNON )
+ {
+ CEffectData data;
+ data.m_nAttachmentIndex = m_nBarrelAttachment;
+ data.m_nEntIndex = pAnim->entindex();
+
+ // FIXME: Create a custom entry here!
+ DispatchEffect( "ChopperMuzzleFlash", data );
+ }
+ else
+ {
+ CEffectData data;
+ data.m_nEntIndex = pAnim->entindex();
+ data.m_nAttachmentIndex = m_nBarrelAttachment;
+ data.m_flScale = 1.0f;
+ data.m_fFlags = MUZZLEFLASH_COMBINE;
+
+ DispatchEffect( "MuzzleFlash", data );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : const char
+//-----------------------------------------------------------------------------
+const char *CFuncTank::GetTracerType( void )
+{
+ switch( m_iEffectHandling )
+ {
+ case EH_AR2:
+ return "AR2Tracer";
+
+ case EH_COMBINE_CANNON:
+ return "HelicopterTracer";
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fire targets and spawn sprites.
+// Input : bulletCount -
+// barrelEnd -
+// forward -
+// pAttacker -
+//-----------------------------------------------------------------------------
+void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
+{
+ // If we have a specific effect handler, apply it's effects
+ if ( m_iEffectHandling == EH_AR2 )
+ {
+ DoMuzzleFlash();
+
+ // Play the AR2 sound
+ EmitSound( "Weapon_functank.Single" );
+ }
+ else if ( m_iEffectHandling == EH_COMBINE_CANNON )
+ {
+ DoMuzzleFlash();
+
+ // Play the cannon sound
+ EmitSound( "NPC_Combine_Cannon.FireBullet" );
+ }
+ else
+ {
+ 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( 5 );
+ pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
+ pSprite->SetScale( m_spriteScale );
+ }
+ }
+
+ if( pAttacker && pAttacker->IsPlayer() )
+ {
+ if ( IsX360() )
+ {
+ UTIL_PlayerByIndex(1)->RumbleEffect( RUMBLE_AR2, 0, RUMBLE_FLAG_RESTART | RUMBLE_FLAG_RANDOM_AMPLITUDE );
+ }
+ else
+ {
+ CSoundEnt::InsertSound( SOUND_MOVE_AWAY, barrelEnd + forward * 32.0f, 32.0f, 0.2f, pAttacker, SOUNDENT_CHANNEL_WEAPON );
+ }
+ }
+
+
+ m_OnFire.FireOutput(this, this);
+ m_bReadyToFire = false;
+}
+
+
+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.85;
+ 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;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CFuncTank::IsEntityInViewCone( CBaseEntity *pEntity )
+{
+ // First check to see if the enemy is in range.
+ Vector vecBarrelEnd = WorldBarrelPosition();
+ float flRange2 = ( pEntity->GetAbsOrigin() - vecBarrelEnd ).LengthSqr();
+
+ if( !(GetSpawnFlags() & SF_TANK_IGNORE_RANGE_IN_VIEWCONE) )
+ {
+ if ( !InRange2( flRange2 ) )
+ return false;
+ }
+
+ // If we're trying to shoot at a player, and we've seen a non-player recently, check the grace period
+ if ( m_flPlayerGracePeriod && pEntity->IsPlayer() && (gpGlobals->curtime - m_flLastSawNonPlayer) < m_flPlayerGracePeriod )
+ {
+ // Grace period is ignored under a certain distance
+ if ( flRange2 > m_flIgnoreGraceUpto )
+ return false;
+ }
+
+ // Check to see if the entity center lies within the yaw and pitch constraints.
+ // This isn't horribly accurate, but should do for now.
+ QAngle angGun;
+ angGun = AimBarrelAt( m_parentMatrix.WorldToLocal( pEntity->GetAbsOrigin() ) );
+
+ // Force the angles to be relative to the center position
+ float flOffsetY = UTIL_AngleDistance( angGun.y, m_yawCenter );
+ float flOffsetX = UTIL_AngleDistance( angGun.x, m_pitchCenter );
+ angGun.y = m_yawCenter + flOffsetY;
+ angGun.x = m_pitchCenter + flOffsetX;
+
+ if ( ( fabs( flOffsetY ) > m_yawRange + m_yawTolerance ) || ( fabs( flOffsetX ) > m_pitchRange + m_pitchTolerance ) )
+ return false;
+
+ // Remember the last time we saw a non-player
+ if ( !pEntity->IsPlayer() )
+ {
+ m_flLastSawNonPlayer = gpGlobals->curtime;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return true if this func tank can see the enemy
+//-----------------------------------------------------------------------------
+bool CFuncTank::HasLOSTo( CBaseEntity *pEntity )
+{
+ if ( !pEntity )
+ return false;
+
+ // Get the barrel position
+ Vector vecBarrelEnd = WorldBarrelPosition();
+ Vector vecTarget = pEntity->BodyTarget( GetAbsOrigin(), false );
+ trace_t tr;
+
+ // Ignore the func_tank and any prop it's parented to
+ CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE );
+
+ // UNDONE: Should this hit BLOCKLOS brushes?
+ AI_TraceLine( vecBarrelEnd, vecTarget, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &tr );
+
+ CBaseEntity *pHitEntity = tr.m_pEnt;
+
+ // Is entity in a vehicle? if so, verify vehicle is target and return if so (so npc shoots at vehicle)
+ CBaseCombatCharacter *pCCEntity = pEntity->MyCombatCharacterPointer();
+ if ( pCCEntity != NULL && pCCEntity->IsInAVehicle() )
+ {
+ // Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is
+ // Also, check to see if the owner of the entity is the vehicle, in which case it's valid too.
+ // This catches vehicles that use bone followers.
+ CBaseEntity *pVehicle = pCCEntity->GetVehicle()->GetVehicleEnt();
+ if ( pHitEntity == pVehicle || ( pHitEntity != NULL && pHitEntity->GetOwnerEntity() == pVehicle ) )
+ return true;
+ }
+
+ return ( tr.fraction == 1.0 || tr.m_pEnt == pEntity );
+}
+
+// #############################################################################
+// CFuncTankGun
+// #############################################################################
+class CFuncTankGun : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankGun, CFuncTank );
+
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
+};
+LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun );
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTankGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
+{
+ int i;
+
+ FireBulletsInfo_t info;
+ info.m_iShots = 1;
+ info.m_vecSrc = barrelEnd;
+ info.m_vecDirShooting = forward;
+ if ( bIgnoreSpread )
+ {
+ info.m_vecSpread = gTankSpread[0];
+ }
+ else
+ {
+ info.m_vecSpread = gTankSpread[m_spread];
+ }
+
+ info.m_flDistance = MAX_TRACE_LENGTH;
+ info.m_iTracerFreq = 1;
+ info.m_flDamage = m_iBulletDamage;
+ info.m_iPlayerDamage = m_iBulletDamageVsPlayer;
+ info.m_pAttacker = pAttacker;
+ info.m_pAdditionalIgnoreEnt = GetParent();
+
+#ifdef HL2_EPISODIC
+ if ( m_iAmmoType != -1 )
+ {
+ for ( i = 0; i < bulletCount; i++ )
+ {
+ info.m_iAmmoType = m_iAmmoType;
+ FireBullets( info );
+ }
+ }
+#else
+ for ( i = 0; i < bulletCount; i++ )
+ {
+ switch( m_bulletType )
+ {
+ case TANK_BULLET_SMALL:
+ info.m_iAmmoType = m_iSmallAmmoType;
+ FireBullets( info );
+ break;
+
+ case TANK_BULLET_MEDIUM:
+ info.m_iAmmoType = m_iMediumAmmoType;
+ FireBullets( info );
+ break;
+
+ case TANK_BULLET_LARGE:
+ info.m_iAmmoType = m_iLargeAmmoType;
+ FireBullets( info );
+ break;
+
+ default:
+ case TANK_BULLET_NONE:
+ break;
+ }
+ }
+#endif // HL2_EPISODIC
+
+ CFuncTank::Fire( bulletCount, barrelEnd, forward, pAttacker, bIgnoreSpread );
+}
+
+// #############################################################################
+// 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, bool bIgnoreSpread );
+
+ 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, bool bIgnoreSpread )
+{
+ // --------------------------------------------------
+ // 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 = SNDLVL_85dB;
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+ }
+ CFuncTank::Fire( bulletCount, barrelEnd, vecForward, pAttacker, bIgnoreSpread );
+}
+
+// #############################################################################
+// 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, bool bIgnoreSpread );
+ void Think( void );
+ CEnvLaser *GetLaser( 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();
+ }
+}
+
+
+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, bool bIgnoreSpread )
+{
+ 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, bIgnoreSpread );
+ }
+}
+
+class CFuncTankRocket : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankRocket, CFuncTank );
+
+ void Precache( void );
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
+ virtual float GetShotSpeed() { return m_flRocketSpeed; }
+
+protected:
+ float m_flRocketSpeed;
+
+ DECLARE_DATADESC();
+};
+
+BEGIN_DATADESC( CFuncTankRocket )
+
+ DEFINE_KEYFIELD( m_flRocketSpeed, FIELD_FLOAT, "rocketspeed" ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket );
+
+void CFuncTankRocket::Precache( void )
+{
+ UTIL_PrecacheOther( "rpg_missile" );
+ CFuncTank::Precache();
+}
+
+void CFuncTankRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
+{
+ CMissile *pRocket = (CMissile *) CBaseEntity::Create( "rpg_missile", barrelEnd, GetAbsAngles(), this );
+
+ pRocket->DumbFire();
+ pRocket->SetNextThink( gpGlobals->curtime + 0.1f );
+ pRocket->SetAbsVelocity( forward * m_flRocketSpeed );
+ if ( GetController() && GetController()->IsPlayer() )
+ {
+ pRocket->SetDamage( m_iBulletDamage );
+ }
+ else
+ {
+ pRocket->SetDamage( m_iBulletDamageVsPlayer );
+ }
+
+ CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread );
+}
+
+
+//-----------------------------------------------------------------------------
+// Airboat gun
+//-----------------------------------------------------------------------------
+class CFuncTankAirboatGun : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankAirboatGun, CFuncTank );
+ DECLARE_DATADESC();
+
+ void Precache( void );
+ virtual void Spawn();
+ virtual void Activate();
+ virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
+ virtual void ControllerPostFrame();
+ virtual void OnStopControlled();
+ virtual const char *GetTracerType( void );
+ virtual Vector WorldBarrelPosition( void );
+ virtual void DoImpactEffect( trace_t &tr, int nDamageType );
+
+private:
+ void CreateSounds();
+ void DestroySounds();
+ void DoMuzzleFlash( );
+ void StartFiring();
+ void StopFiring();
+
+ CSoundPatch *m_pGunFiringSound;
+ float m_flNextHeavyShotTime;
+ bool m_bIsFiring;
+
+ string_t m_iszAirboatGunModel;
+ CHandle<CBaseAnimating> m_hAirboatGunModel;
+ int m_nGunBarrelAttachment;
+ float m_flLastImpactEffectTime;
+};
+
+
+//-----------------------------------------------------------------------------
+// Save/load:
+//-----------------------------------------------------------------------------
+BEGIN_DATADESC( CFuncTankAirboatGun )
+
+ DEFINE_SOUNDPATCH( m_pGunFiringSound ),
+ DEFINE_FIELD( m_flNextHeavyShotTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ),
+ DEFINE_KEYFIELD( m_iszAirboatGunModel, FIELD_STRING, "airboat_gun_model" ),
+// DEFINE_FIELD( m_hAirboatGunModel, FIELD_EHANDLE ),
+// DEFINE_FIELD( m_nGunBarrelAttachment, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flLastImpactEffectTime, FIELD_TIME ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( func_tankairboatgun, CFuncTankAirboatGun );
+
+
+//-----------------------------------------------------------------------------
+// Precache:
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::Precache( void )
+{
+ BaseClass::Precache();
+ PrecacheScriptSound( "Airboat.FireGunLoop" );
+ PrecacheScriptSound( "Airboat.FireGunRevDown");
+ CreateSounds();
+}
+
+
+//-----------------------------------------------------------------------------
+// Precache:
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::Spawn( void )
+{
+ BaseClass::Spawn();
+ m_flNextHeavyShotTime = 0.0f;
+ m_bIsFiring = false;
+ m_flLastImpactEffectTime = -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Attachment indices
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::Activate()
+{
+ BaseClass::Activate();
+
+ if ( m_iszAirboatGunModel != NULL_STRING )
+ {
+ m_hAirboatGunModel = dynamic_cast<CBaseAnimating*>( gEntList.FindEntityByName( NULL, m_iszAirboatGunModel ) );
+ if ( m_hAirboatGunModel )
+ {
+ m_nGunBarrelAttachment = m_hAirboatGunModel->LookupAttachment( "muzzle" );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Create/destroy looping sounds
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::CreateSounds()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ CPASAttenuationFilter filter( this );
+ if (!m_pGunFiringSound)
+ {
+ m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "Airboat.FireGunLoop" );
+ controller.Play( m_pGunFiringSound, 0, 100 );
+ }
+}
+
+void CFuncTankAirboatGun::DestroySounds()
+{
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+
+ controller.SoundDestroy( m_pGunFiringSound );
+ m_pGunFiringSound = NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Stop Firing
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::StartFiring()
+{
+ if ( !m_bIsFiring )
+ {
+ CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController();
+ float flVolume = pController->SoundGetVolume( m_pGunFiringSound );
+ pController->SoundChangeVolume( m_pGunFiringSound, 1.0f, 0.1f * (1.0f - flVolume) );
+ m_bIsFiring = true;
+ }
+}
+
+void CFuncTankAirboatGun::StopFiring()
+{
+ if ( m_bIsFiring )
+ {
+ CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController();
+ float flVolume = pController->SoundGetVolume( m_pGunFiringSound );
+ pController->SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f * flVolume );
+ EmitSound( "Airboat.FireGunRevDown" );
+ m_bIsFiring = false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Maintains airboat gun sounds
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::ControllerPostFrame( void )
+{
+ if ( IsPlayerManned() )
+ {
+ CBasePlayer *pPlayer = static_cast<CBasePlayer*>( GetController() );
+ if ( pPlayer->m_nButtons & IN_ATTACK )
+ {
+ StartFiring();
+ }
+ else
+ {
+ StopFiring();
+ }
+ }
+
+ BaseClass::ControllerPostFrame();
+}
+
+
+//-----------------------------------------------------------------------------
+// Stop controlled
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::OnStopControlled()
+{
+ StopFiring();
+ BaseClass::OnStopControlled();
+}
+
+
+//-----------------------------------------------------------------------------
+// Barrel position
+//-----------------------------------------------------------------------------
+Vector CFuncTankAirboatGun::WorldBarrelPosition( void )
+{
+ if ( !m_hAirboatGunModel || (m_nGunBarrelAttachment == 0) )
+ {
+ return BaseClass::WorldBarrelPosition();
+ }
+
+ Vector vecOrigin;
+ m_hAirboatGunModel->GetAttachment( m_nGunBarrelAttachment, vecOrigin );
+ return vecOrigin;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CFuncTankAirboatGun::GetTracerType( void )
+{
+ if ( gpGlobals->curtime >= m_flNextHeavyShotTime )
+ return "AirboatGunHeavyTracer";
+
+ return "AirboatGunTracer";
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::DoMuzzleFlash( void )
+{
+ if ( m_hAirboatGunModel && (m_nGunBarrelAttachment != 0) )
+ {
+ CEffectData data;
+ data.m_nEntIndex = m_hAirboatGunModel->entindex();
+ data.m_nAttachmentIndex = m_nGunBarrelAttachment;
+ data.m_flScale = 1.0f;
+ DispatchEffect( "AirboatMuzzleFlash", data );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Allows the shooter to change the impact effect of his bullets
+//-----------------------------------------------------------------------------
+void CFuncTankAirboatGun::DoImpactEffect( trace_t &tr, int nDamageType )
+{
+ // The airboat spits out so much crap that we need to do cheaper versions
+ // of the impact effects. Also, we need to do less of them.
+ if ( m_flLastImpactEffectTime == gpGlobals->curtime )
+ return;
+
+ m_flLastImpactEffectTime = gpGlobals->curtime;
+ UTIL_ImpactTrace( &tr, nDamageType, "AirboatGunImpact" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Fires bullets
+//-----------------------------------------------------------------------------
+#define AIRBOAT_GUN_HEAVY_SHOT_INTERVAL 0.2f
+
+void CFuncTankAirboatGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
+{
+ CAmmoDef *pAmmoDef = GetAmmoDef();
+ int ammoType = pAmmoDef->Index( "AirboatGun" );
+
+ FireBulletsInfo_t info;
+ info.m_vecSrc = barrelEnd;
+ info.m_vecDirShooting = forward;
+ info.m_flDistance = 4096;
+ info.m_iAmmoType = ammoType;
+
+ if ( gpGlobals->curtime >= m_flNextHeavyShotTime )
+ {
+ info.m_iShots = 1;
+ info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
+ info.m_flDamageForceScale = 1000.0f;
+ }
+ else
+ {
+ info.m_iShots = 2;
+ info.m_vecSpread = VECTOR_CONE_5DEGREES;
+ }
+
+ FireBullets( info );
+
+ DoMuzzleFlash();
+
+ // NOTE: This must occur after FireBullets
+ if ( gpGlobals->curtime >= m_flNextHeavyShotTime )
+ {
+ m_flNextHeavyShotTime = gpGlobals->curtime + AIRBOAT_GUN_HEAVY_SHOT_INTERVAL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// APC Rocket
+//-----------------------------------------------------------------------------
+#define DEATH_VOLLEY_MISSILE_COUNT 10
+#define DEATH_VOLLEY_MIN_FIRE_RATE 3
+#define DEATH_VOLLEY_MAX_FIRE_RATE 6
+
+class CFuncTankAPCRocket : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankAPCRocket, CFuncTank );
+
+ void Precache( void );
+ virtual void Spawn();
+ virtual void UpdateOnRemove();
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
+ virtual void Think();
+ virtual float GetShotSpeed() { return m_flRocketSpeed; }
+
+protected:
+ void InputDeathVolley( inputdata_t &inputdata );
+ void FireDying( const Vector &barrelEnd );
+
+ EHANDLE m_hLaserDot;
+ float m_flRocketSpeed;
+ int m_nSide;
+ int m_nBurstCount;
+ bool m_bDying;
+
+ DECLARE_DATADESC();
+};
+
+
+BEGIN_DATADESC( CFuncTankAPCRocket )
+
+ DEFINE_KEYFIELD( m_flRocketSpeed, FIELD_FLOAT, "rocketspeed" ),
+ DEFINE_FIELD( m_hLaserDot, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_nSide, FIELD_INTEGER ),
+ DEFINE_KEYFIELD( m_nBurstCount, FIELD_INTEGER, "burstcount" ),
+ DEFINE_FIELD( m_bDying, FIELD_BOOLEAN ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "DeathVolley", InputDeathVolley ),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( func_tankapcrocket, CFuncTankAPCRocket );
+
+void CFuncTankAPCRocket::Precache( void )
+{
+ UTIL_PrecacheOther( "apc_missile" );
+
+ PrecacheScriptSound( "PropAPC.FireCannon" );
+
+ CFuncTank::Precache();
+}
+
+void CFuncTankAPCRocket::Spawn( void )
+{
+ BaseClass::Spawn();
+ AddEffects( EF_NODRAW );
+ m_nSide = 0;
+ m_bDying = false;
+ m_hLaserDot = CreateLaserDot( GetAbsOrigin(), this, false );
+ m_nBulletCount = m_nBurstCount;
+ SetSolid( SOLID_NONE );
+ SetLocalVelocity( vec3_origin );
+}
+
+void CFuncTankAPCRocket::UpdateOnRemove( void )
+{
+ if ( m_hLaserDot )
+ {
+ UTIL_Remove( m_hLaserDot );
+ m_hLaserDot = NULL;
+ }
+ BaseClass::UpdateOnRemove();
+}
+
+void CFuncTankAPCRocket::FireDying( const Vector &barrelEnd )
+{
+ Vector vecDir;
+ vecDir.Random( -1.0f, 1.0f );
+ if ( vecDir.z < 0.0f )
+ {
+ vecDir.z *= -1.0f;
+ }
+
+ VectorNormalize( vecDir );
+
+ Vector vecVelocity;
+ VectorMultiply( vecDir, m_flRocketSpeed * random->RandomFloat( 0.75f, 1.25f ), vecVelocity );
+
+ QAngle angles;
+ VectorAngles( vecDir, angles );
+
+ CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( barrelEnd, angles, vecVelocity, this );
+ float flDeathTime = random->RandomFloat( 0.3f, 0.5f );
+ if ( random->RandomFloat( 0.0f, 1.0f ) < 0.3f )
+ {
+ pRocket->ExplodeDelay( flDeathTime );
+ }
+ else
+ {
+ pRocket->AugerDelay( flDeathTime );
+ }
+
+ // Make erratic firing
+ m_fireRate = random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_RATE, DEATH_VOLLEY_MAX_FIRE_RATE );
+ if ( --m_nBulletCount <= 0 )
+ {
+ UTIL_Remove( this );
+ }
+}
+
+void CFuncTankAPCRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
+{
+ static float s_pSide[] = { 0.966, 0.866, 0.5, -0.5, -0.866, -0.966 };
+
+ Vector vecDir;
+ CrossProduct( Vector( 0, 0, 1 ), forward, vecDir );
+ vecDir.z = 1.0f;
+ vecDir.x *= s_pSide[m_nSide];
+ vecDir.y *= s_pSide[m_nSide];
+ if ( ++m_nSide >= 6 )
+ {
+ m_nSide = 0;
+ }
+
+ VectorNormalize( vecDir );
+
+ Vector vecVelocity;
+ VectorMultiply( vecDir, m_flRocketSpeed, vecVelocity );
+
+ QAngle angles;
+ VectorAngles( vecDir, angles );
+
+ CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( barrelEnd, angles, vecVelocity, this );
+ pRocket->IgniteDelay();
+
+ CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread );
+
+ if ( --m_nBulletCount <= 0 )
+ {
+ m_nBulletCount = m_nBurstCount;
+
+ // This will cause it to wait for a little while before shooting
+ m_fireLast += random->RandomFloat( 2.0f, 3.0f );
+ }
+ EmitSound( "PropAPC.FireCannon" );
+}
+
+void CFuncTankAPCRocket::Think()
+{
+ // Inert if we're carried...
+ if ( GetMoveParent() && GetMoveParent()->GetMoveParent() )
+ {
+ SetNextThink( gpGlobals->curtime + 0.5f );
+ return;
+ }
+
+ BaseClass::Think();
+ m_hLaserDot->SetAbsOrigin( m_sightOrigin );
+ SetLaserDotTarget( m_hLaserDot, m_hFuncTankTarget );
+ EnableLaserDot( m_hLaserDot, m_hFuncTankTarget != NULL );
+
+ if ( m_bDying )
+ {
+ FireDying( WorldBarrelPosition() );
+ return;
+ }
+}
+
+
+void CFuncTankAPCRocket::InputDeathVolley( inputdata_t &inputdata )
+{
+ if ( !m_bDying )
+ {
+ m_fireRate = random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_RATE, DEATH_VOLLEY_MAX_FIRE_RATE );
+ SetNextAttack( gpGlobals->curtime + (1.0f / m_fireRate ) );
+ m_nBulletCount = DEATH_VOLLEY_MISSILE_COUNT;
+ m_bDying = true;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Mortar shell
+//-----------------------------------------------------------------------------
+class CMortarShell : public CBaseEntity
+{
+public:
+ DECLARE_CLASS( CMortarShell, CBaseEntity );
+
+ static CMortarShell *Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound );
+
+ void Spawn( void );
+ void Precache( void );
+ void Impact( void );
+ void Warn( void );
+ void FlyThink( void );
+ void FadeThink( void );
+ int UpdateTransmitState( void );
+
+private:
+
+ void FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal );
+
+ float m_flFadeTime;
+ float m_flImpactTime;
+ float m_flWarnTime;
+ float m_flNPCWarnTime;
+ string_t m_warnSound;
+ int m_iSpriteTexture;
+ bool m_bHasWarned;
+ Vector m_vecFiredFrom;
+ Vector m_vecFlyDir;
+ float m_flSpawnedTime;
+
+ CHandle<CBeam> m_pBeamEffect[4];
+
+ CNetworkVar( float, m_flLifespan );
+ CNetworkVar( float, m_flRadius );
+ CNetworkVar( Vector, m_vecSurfaceNormal );
+
+ DECLARE_DATADESC();
+ DECLARE_SERVERCLASS();
+};
+
+LINK_ENTITY_TO_CLASS( mortarshell, CMortarShell );
+
+BEGIN_DATADESC( CMortarShell )
+ DEFINE_FIELD( m_flImpactTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flFadeTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flWarnTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNPCWarnTime, FIELD_TIME ),
+ DEFINE_FIELD( m_warnSound, FIELD_STRING ),
+ DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bHasWarned, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flLifespan, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecFiredFrom, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecFlyDir, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flSpawnedTime, FIELD_TIME ),
+ DEFINE_AUTO_ARRAY( m_pBeamEffect, FIELD_EHANDLE),
+ DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecSurfaceNormal, FIELD_VECTOR ),
+
+ DEFINE_FUNCTION( FlyThink ),
+ DEFINE_FUNCTION( FadeThink ),
+END_DATADESC()
+
+IMPLEMENT_SERVERCLASS_ST( CMortarShell, DT_MortarShell )
+ SendPropFloat( SENDINFO( m_flLifespan ), -1, SPROP_NOSCALE ),
+ SendPropFloat( SENDINFO( m_flRadius ), -1, SPROP_NOSCALE ),
+ SendPropVector( SENDINFO( m_vecSurfaceNormal ), 0, SPROP_NORMAL ),
+END_SEND_TABLE()
+
+#define MORTAR_TEST_RADIUS 16.0f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &initialPos -
+// *endPos -
+// *endNormal -
+//-----------------------------------------------------------------------------
+void CMortarShell::FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal )
+{
+ Vector vecStartOffset;
+
+ vecStartOffset = initialPos + ( initialNormal * 1.0f );
+
+ trace_t tr;
+ UTIL_TraceLine( vecStartOffset, vecStartOffset - Vector( 0, 0, 256 ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if ( tr.fraction < 1.0f )
+ {
+ if ( endPos )
+ {
+ *endPos = tr.endpos + ( initialNormal * 16.0f );
+ }
+
+ if ( endNormal )
+ {
+ *endNormal = tr.plane.normal;
+ }
+ }
+ else
+ {
+ if ( endPos )
+ {
+ *endPos = initialPos;
+ }
+
+ if ( endNormal )
+ {
+ *endNormal = initialNormal;
+ }
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+#define MORTAR_BLAST_DAMAGE 50
+#define MORTAR_BLAST_HEIGHT 7500
+
+CMortarShell *CMortarShell::Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound )
+{
+ CMortarShell *pShell = (CMortarShell *)CreateEntityByName("mortarshell" );
+
+ // Place the mortar shell at the target location so that it can make the sound and explode.
+ trace_t tr;
+ UTIL_TraceLine( vecTarget, vecTarget + ( vecShotDir * 128.0f ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &tr );
+
+ Vector targetPos, targetNormal;
+ pShell->FixUpImpactPoint( tr.endpos, tr.plane.normal, &targetPos, &targetNormal );
+
+ UTIL_SetOrigin( pShell, targetPos );
+
+ Vector vecStartSkew, vecEndSkew;
+
+ vecStartSkew = targetPos - vecStart;
+ vecStartSkew[2] = 0.0f;
+ float skewLength = VectorNormalize( vecStartSkew );
+
+ vecEndSkew = -vecStartSkew * ( skewLength * 0.25f );
+ vecStartSkew *= skewLength * 0.1f;
+
+ // Muzzleflash beam
+ pShell->m_pBeamEffect[0] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 );
+ pShell->m_pBeamEffect[0]->PointsInit( vecStart, vecStart + Vector( vecStartSkew[0], vecStartSkew[1], MORTAR_BLAST_HEIGHT ) );
+ pShell->m_pBeamEffect[0]->SetColor( 16, 16, 8 );
+ pShell->m_pBeamEffect[0]->SetBrightness( 0 );
+ pShell->m_pBeamEffect[0]->SetNoise( 0 );
+ pShell->m_pBeamEffect[0]->SetBeamFlag( FBEAM_SHADEOUT );
+ pShell->m_pBeamEffect[0]->SetWidth( 64.0f );
+ pShell->m_pBeamEffect[0]->SetEndWidth( 64.0f );
+
+ pShell->m_pBeamEffect[1] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 );
+ pShell->m_pBeamEffect[1]->PointsInit( vecStart, vecStart + Vector( vecStartSkew[0], vecStartSkew[1], MORTAR_BLAST_HEIGHT ) );
+ pShell->m_pBeamEffect[1]->SetColor( 255, 255, 255 );
+ pShell->m_pBeamEffect[1]->SetBrightness( 0 );
+ pShell->m_pBeamEffect[1]->SetNoise( 0 );
+ pShell->m_pBeamEffect[1]->SetBeamFlag( FBEAM_SHADEOUT );
+ pShell->m_pBeamEffect[1]->SetWidth( 8.0f );
+ pShell->m_pBeamEffect[1]->SetEndWidth( 8.0f );
+
+ trace_t skyTrace;
+ UTIL_TraceLine( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &skyTrace );
+
+ // We must touch the sky to make this beam
+ if ( skyTrace.fraction <= 1.0f && skyTrace.surface.flags & SURF_SKY )
+ {
+ // Impact point beam
+ pShell->m_pBeamEffect[2] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 );
+ pShell->m_pBeamEffect[2]->PointsInit( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ) );
+ pShell->m_pBeamEffect[2]->SetColor( 16, 16, 8 );
+ pShell->m_pBeamEffect[2]->SetBrightness( 0 );
+ pShell->m_pBeamEffect[2]->SetNoise( 0 );
+ pShell->m_pBeamEffect[2]->SetBeamFlag( FBEAM_SHADEOUT );
+ pShell->m_pBeamEffect[2]->SetWidth( 32.0f );
+ pShell->m_pBeamEffect[2]->SetEndWidth( 32.0f );
+
+ pShell->m_pBeamEffect[3] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 );
+ pShell->m_pBeamEffect[3]->PointsInit( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ) );
+ pShell->m_pBeamEffect[3]->SetColor( 255, 255, 255 );
+ pShell->m_pBeamEffect[3]->SetBrightness( 0 );
+ pShell->m_pBeamEffect[3]->SetNoise( 0 );
+ pShell->m_pBeamEffect[3]->SetBeamFlag( FBEAM_SHADEOUT );
+ pShell->m_pBeamEffect[3]->SetWidth( 4.0f );
+ pShell->m_pBeamEffect[3]->SetEndWidth( 4.0f );
+ }
+ else
+ {
+ // Mark these as not being used
+ pShell->m_pBeamEffect[2] = NULL;
+ pShell->m_pBeamEffect[3] = NULL;
+ }
+
+ pShell->m_vecFiredFrom = vecStart;
+ pShell->m_flLifespan = flImpactDelay;
+ pShell->m_flImpactTime = gpGlobals->curtime + flImpactDelay;
+ pShell->m_flWarnTime = pShell->m_flImpactTime - flWarnDelay;
+ pShell->m_flNPCWarnTime = pShell->m_flWarnTime - 0.5;
+ pShell->m_warnSound = warnSound;
+ pShell->Spawn();
+
+ // Save off the impact normal
+ pShell->m_vecSurfaceNormal = targetNormal;
+ pShell->m_flRadius = MORTAR_BLAST_RADIUS;
+
+ return pShell;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CMortarShell::Precache()
+{
+ m_iSpriteTexture = PrecacheModel( "sprites/physbeam.vmt" );
+
+ PrecacheScriptSound( "Weapon_Mortar.Impact" );
+ PrecacheMaterial( "effects/ar2ground2" );
+
+ if ( NULL_STRING != m_warnSound )
+ {
+ PrecacheScriptSound( STRING( m_warnSound ) );
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Send even though we don't have a model
+//------------------------------------------------------------------------------
+int CMortarShell::UpdateTransmitState( void )
+{
+ return SetTransmitState( FL_EDICT_PVSCHECK );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CMortarShell::Spawn()
+{
+ Precache();
+
+ AddEffects( EF_NODRAW );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ Vector mins( -MORTAR_BLAST_RADIUS, -MORTAR_BLAST_RADIUS, -MORTAR_BLAST_RADIUS );
+ Vector maxs( MORTAR_BLAST_RADIUS, MORTAR_BLAST_RADIUS, MORTAR_BLAST_RADIUS );
+
+ UTIL_SetSize( this, mins, maxs );
+
+ m_vecFlyDir = GetAbsOrigin() - m_vecFiredFrom;
+ VectorNormalize( m_vecFlyDir );
+
+ m_flSpawnedTime = gpGlobals->curtime;
+
+ SetThink( &CMortarShell::FlyThink );
+ SetNextThink( gpGlobals->curtime );
+
+ // No model but we still need to force this!
+ AddEFlags( EFL_FORCE_CHECK_TRANSMIT );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : type -
+// steps -
+// bias -
+//-----------------------------------------------------------------------------
+ConVar curve_bias( "curve_bias", "0.5" );
+
+enum
+{
+ CURVE_BIAS,
+ CURVE_GAIN,
+ CURVE_SMOOTH,
+ CURVE_SMOOTH_TWEAK,
+};
+
+void UTIL_VisualizeCurve( int type, int steps, float bias )
+{
+ CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
+ Vector vForward, vRight, vUp;
+
+ pPlayer->EyeVectors( &vForward, &vRight, &vUp );
+
+ Vector renderOrigin = pPlayer->EyePosition() + ( vForward * 512.0f );
+
+ float renderScale = 8.0f;
+ float lastPerc, perc;
+
+ Vector renderOffs, lastRenderOffs = vec3_origin;
+
+ for ( int i = 0; i < steps; i++ )
+ {
+ perc = RemapValClamped( i, 0, steps-1, 0.0f, 1.0f );
+
+ switch( type )
+ {
+ case CURVE_BIAS:
+ perc = Bias( perc, bias );
+ break;
+
+ case CURVE_GAIN:
+ perc = Gain( perc, bias );
+ break;
+
+ case CURVE_SMOOTH:
+ perc = SmoothCurve( perc );
+ break;
+
+ case CURVE_SMOOTH_TWEAK:
+ perc = SmoothCurve_Tweak( perc, bias, 0.9f );
+ break;
+ }
+
+ renderOffs = ( vRight * (-steps*0.5f) * renderScale ) + ( vUp * (renderScale*-(steps*0.5f)) )+ ( vRight * i * renderScale ) + ( vUp * perc * (renderScale*steps) );
+
+ NDebugOverlay::Cross3D( renderOrigin + renderOffs, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, true, 0.05f );
+
+ if ( i > 0 )
+ {
+ NDebugOverlay::Line( renderOrigin + renderOffs, renderOrigin + lastRenderOffs, 255, 0, 0, true, 0.05f );
+ }
+
+ lastRenderOffs = renderOffs;
+ lastPerc = perc;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CMortarShell::FlyThink()
+{
+ SetNextThink( gpGlobals->curtime + 0.05 );
+
+ if ( gpGlobals->curtime > m_flNPCWarnTime )
+ {
+ // Warn the AI. Make this radius a little larger than the explosion will be, and make the sound last a little longer.
+ CSoundEnt::InsertSound ( SOUND_DANGER | SOUND_CONTEXT_MORTAR, GetAbsOrigin(), MORTAR_BLAST_RADIUS * 1.25, (m_flImpactTime - m_flNPCWarnTime) + 0.15 );
+ m_flNPCWarnTime = FLT_MAX;
+ }
+
+ //UTIL_VisualizeCurve( CURVE_GAIN, 64, curve_bias.GetFloat() );
+
+ float lifePerc = 1.0f - ( ( m_flImpactTime - gpGlobals->curtime ) / ( m_flImpactTime - m_flSpawnedTime ) );
+
+ lifePerc = clamp( lifePerc, 0.0f, 1.0f );
+
+ float curve1 = Bias( lifePerc, 0.75f );
+
+ // Beam updates START
+
+ m_pBeamEffect[0]->SetBrightness( 255 * curve1 );
+ m_pBeamEffect[0]->SetWidth( 64.0f * curve1 );
+ m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 );
+
+ m_pBeamEffect[1]->SetBrightness( 255 * curve1 );
+ m_pBeamEffect[1]->SetWidth( 8.0f * curve1 );
+ m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 );
+
+ float curve2 = Bias( lifePerc, 0.1f );
+
+ if ( m_pBeamEffect[2] )
+ {
+ m_pBeamEffect[2]->SetBrightness( 255 * curve2 );
+ m_pBeamEffect[2]->SetWidth( 32.0f * curve2 );
+ m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 );
+ }
+
+ if ( m_pBeamEffect[3] )
+ {
+ m_pBeamEffect[3]->SetBrightness( 255 * curve2 );
+ m_pBeamEffect[3]->SetWidth( 8.0f * curve2 );
+ m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 );
+ }
+
+ // Beam updates END
+
+ if( !m_bHasWarned && gpGlobals->curtime > m_flWarnTime )
+ {
+ Warn();
+ }
+
+ if( gpGlobals->curtime > m_flImpactTime )
+ {
+ Impact();
+ }
+
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CMortarShell::Warn( void )
+{
+ if ( m_warnSound != NULL_STRING )
+ {
+ CPASAttenuationFilter filter( this );
+
+ EmitSound_t ep;
+ ep.m_nChannel = CHAN_WEAPON;
+ ep.m_pSoundName = (char*)STRING(m_warnSound);
+ ep.m_flVolume = 1.0f;
+ ep.m_SoundLevel = SNDLVL_NONE;
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+ m_bHasWarned = true;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CMortarShell::Impact( void )
+{
+ // Fire the bullets
+ Vector vecSrc, vecShootDir;
+
+ float flRadius = MORTAR_BLAST_RADIUS;
+
+ trace_t tr;
+ UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 128 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
+
+ UTIL_DecalTrace( &tr, "Scorch" );
+
+ // Send the effect over
+ CEffectData data;
+
+ // Do an extra effect if we struck the world
+ if ( tr.m_pEnt && tr.m_pEnt->IsWorld() )
+ {
+ data.m_flRadius = flRadius * 0.5f;
+ data.m_vNormal = tr.plane.normal;
+ data.m_vOrigin = tr.endpos;
+
+ DispatchEffect( "AR2Explosion", data );
+ }
+
+ //Shockring
+ CBroadcastRecipientFilter filter2;
+ te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin
+ 8.0f, //start radius
+ flRadius * 2, //end radius
+ m_iSpriteTexture, //texture
+ 0, //halo index
+ 0, //start frame
+ 2, //framerate
+ 0.2f, //life
+ 32, //width
+ 0, //spread
+ 0, //amplitude
+ 255, //r
+ 255, //g
+ 225, //b
+ 32, //a
+ 0, //speed
+ FBEAM_FADEOUT
+ );
+
+ //Shockring
+ te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin
+ 8.0f, //start radius
+ flRadius, //end radius
+ m_iSpriteTexture, //texture
+ 0, //halo index
+ 0, //start frame
+ 2, //framerate
+ 0.2f, //life
+ 64, //width
+ 0, //spread
+ 0, //amplitude
+ 255, //r
+ 255, //g
+ 225, //b
+ 64, //a
+ 0, //speed
+ FBEAM_FADEOUT
+ );
+
+ RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), MORTAR_BLAST_DAMAGE, (DMG_BLAST|DMG_DISSOLVE) ), GetAbsOrigin(), MORTAR_BLAST_RADIUS, CLASS_NONE, NULL );
+
+ EmitSound( "Weapon_Mortar.Impact" );
+
+ UTIL_ScreenShake( GetAbsOrigin(), 10, 60, 1.0, 550, SHAKE_START, false );
+
+ //Fade the beams over time!
+ m_flFadeTime = gpGlobals->curtime;
+
+ SetThink( &CMortarShell::FadeThink );
+ SetNextThink( gpGlobals->curtime + 0.05f );
+}
+
+#define MORTAR_FADE_LENGTH 1.0f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMortarShell::FadeThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.05f );
+
+ float lifePerc = 1.0f - ( ( gpGlobals->curtime - m_flFadeTime ) / MORTAR_FADE_LENGTH );
+
+ lifePerc = clamp( lifePerc, 0.0f, 1.0f );
+
+ float curve1 = Bias( lifePerc, 0.1f );
+
+ // Beam updates START
+
+ m_pBeamEffect[0]->SetBrightness( 255 * curve1 );
+ m_pBeamEffect[0]->SetWidth( 64.0f * curve1 );
+ m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 );
+
+ m_pBeamEffect[1]->SetBrightness( 255 * curve1 );
+ m_pBeamEffect[1]->SetWidth( 8.0f * curve1 );
+ m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 );
+
+ float curve2 = Bias( lifePerc, 0.25f );
+
+ if ( m_pBeamEffect[2] )
+ {
+ m_pBeamEffect[2]->SetBrightness( 255 * curve2 );
+ m_pBeamEffect[2]->SetWidth( 32.0f * curve2 );
+ m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 );
+ }
+
+ if ( m_pBeamEffect[3] )
+ {
+ m_pBeamEffect[3]->SetBrightness( 255 * curve2 );
+ m_pBeamEffect[3]->SetWidth( 8.0f * curve2 );
+ m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 );
+ }
+
+ // Beam updates END
+
+ if ( gpGlobals->curtime > ( m_flFadeTime + MORTAR_FADE_LENGTH ) )
+ {
+ UTIL_Remove( m_pBeamEffect[0] );
+ UTIL_Remove( m_pBeamEffect[1] );
+ UTIL_Remove( m_pBeamEffect[2] );
+ UTIL_Remove( m_pBeamEffect[3] );
+
+ SetThink(NULL);
+ UTIL_Remove( this );
+ }
+}
+
+//=========================================================
+//=========================================================
+class CFuncTankMortar : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankMortar, CFuncTank );
+
+ CFuncTankMortar() { m_fLastShotMissed = false; }
+
+ 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, bool bIgnoreSpread );
+ void ShootGun(void);
+ void Spawn();
+ void SetNextAttack( float flWait );
+
+ // Input handlers.
+ void InputShootGun( inputdata_t &inputdata );
+ void InputFireAtWill( inputdata_t &inputdata );
+
+ DECLARE_DATADESC();
+
+ int m_Magnitude;
+ float m_fireDelay;
+ string_t m_fireStartSound;
+ //string_t m_fireEndSound;
+
+ string_t m_incomingSound;
+ float m_flWarningTime;
+ float m_flFireVariance;
+
+ bool m_fLastShotMissed;
+
+ // 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_KEYFIELD( m_incomingSound, FIELD_STRING, "incomingsound" ),
+ DEFINE_KEYFIELD( m_flWarningTime, FIELD_TIME, "warningtime" ),
+ DEFINE_KEYFIELD( m_flFireVariance, FIELD_TIME, "firevariance" ),
+
+ DEFINE_FIELD( m_fLastShotMissed, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_pAttacker, FIELD_CLASSPTR ),
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "ShootGun", InputShootGun ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "FireAtWill", InputFireAtWill ),
+END_DATADESC()
+
+
+void CFuncTankMortar::Spawn()
+{
+ BaseClass::Spawn();
+
+ m_takedamage = DAMAGE_NO;
+}
+
+void CFuncTankMortar::Precache( void )
+{
+ if ( m_fireStartSound != NULL_STRING )
+ PrecacheScriptSound( STRING(m_fireStartSound) );
+ //if ( m_fireEndSound != NULL_STRING )
+ // PrecacheScriptSound( STRING(m_fireEndSound) );
+ if ( m_incomingSound != NULL_STRING )
+ PrecacheScriptSound( STRING(m_incomingSound) );
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CFuncTankMortar::SetNextAttack( float flWait )
+{
+ if ( m_flFireVariance > 0.09 )
+ flWait += random->RandomFloat( -m_flFireVariance, m_flFireVariance );
+ BaseClass::SetNextAttack( flWait );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler to make the tank shoot.
+//-----------------------------------------------------------------------------
+void CFuncTankMortar::InputShootGun( inputdata_t &inputdata )
+{
+ ShootGun();
+}
+
+//-----------------------------------------------------------------------------
+// This mortar can fire the next round as soon as it is ready. This is not a
+// 'sticky' state, it just allows us to get the next shot off as soon as the
+// tank is on target. great for scripted applications where you need a shot as
+// soon as you can get it.
+//-----------------------------------------------------------------------------
+void CFuncTankMortar::InputFireAtWill( inputdata_t &inputdata )
+{
+ SetNextAttack( gpGlobals->curtime );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CFuncTankMortar::ShootGun( void )
+{
+ Vector forward;
+ AngleVectors( GetLocalAngles(), &forward );
+ UpdateMatrix();
+ forward = m_parentMatrix.ApplyRotation( forward );
+
+ Fire( 1, WorldBarrelPosition(), forward, m_pAttacker, false );
+}
+
+
+void CFuncTankMortar::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker )
+{
+ if ( gpGlobals->curtime > GetNextAttack() )
+ {
+ ShootGun();
+ m_fireLast = gpGlobals->curtime;
+ SetNextAttack( gpGlobals->curtime + (1.0 / m_fireRate ) );
+ }
+ else
+ {
+ m_fireLast = gpGlobals->curtime;
+ }
+}
+
+void CFuncTankMortar::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread )
+{
+ Vector vecProjectedPosition = vec3_invalid;
+ trace_t tr;
+
+ if ( m_hTarget )
+ {
+ float leadTime = (m_fireDelay * 1.1);
+
+ if ( m_hTarget->IsNPC() ) // Give NPCs a little extra grace
+ leadTime = 1.25;
+
+ Vector vLead = m_hTarget->GetSmoothedVelocity() * leadTime;
+ Vector vNoise;
+
+ vecProjectedPosition = m_hTarget->WorldSpaceCenter() + vLead;
+ vNoise.AsVector2D().Random( -6*12, 6*12);
+ vNoise.z = 0;
+
+ if( m_hTarget->Classify() != CLASS_BULLSEYE )
+ {
+ // Don't apply noise when attacking a bullseye.
+ vecProjectedPosition += vNoise;
+ }
+ }
+ else if ( IsPlayerManned() )
+ {
+ CalcPlayerCrosshairTarget( &vecProjectedPosition );
+ }
+ else if ( IsNPCManned() )
+ {
+ CalcNPCEnemyTarget( &vecProjectedPosition );
+ //vecProjectedPosition += GetEnemy()->GetSmoothedVelocity() * (m_fireDelay * 1.1);
+ }
+ else
+ return;
+
+ #define TARGET_SEARCH_DEPTH 100
+
+ // find something interesting to shoot at near the projected position.
+ Vector delta;
+
+ // Make a really rough approximation of the last half of the mortar trajectory and trace it.
+ // Do this so that mortars fired into windows land on rooftops, and that targets projected
+ // inside buildings (or out of the world) clip to the world. (usually a building facade)
+
+ // Find halfway between the mortar and the target.
+ Vector vecSpot = ( vecProjectedPosition + GetAbsOrigin() ) * 0.5;
+ vecSpot.z = GetAbsOrigin().z;
+
+ // Trace up to find the fake 'apex' of the shell. The skybox or 1024 units, whichever comes first.
+ UTIL_TraceLine( vecSpot, vecSpot + Vector(0, 0, 1024), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
+ vecSpot = tr.endpos;
+
+ //NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, false, 5 );
+
+ // Now trace from apex to target
+ UTIL_TraceLine( vecSpot, vecProjectedPosition, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
+
+ if( mortar_visualize.GetBool() )
+ {
+ NDebugOverlay::Line( tr.startpos, tr.endpos, 255,0,0, false, 5 );
+ }
+
+ 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_NONE;
+
+ EmitSound( filter, entindex(), ep );
+ }
+
+ Vector vecFinalDir = tr.endpos - tr.startpos;
+ VectorNormalize( vecFinalDir );
+
+ CMortarShell::Create( barrelEnd, tr.endpos, vecFinalDir, m_fireDelay, m_flWarningTime, m_incomingSound );
+ BaseClass::Fire( bulletCount, barrelEnd, vecForward, this, bIgnoreSpread );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Func tank that fires physics cannisters placed on it
+//-----------------------------------------------------------------------------
+class CFuncTankPhysCannister : public CFuncTank
+{
+public:
+ DECLARE_CLASS( CFuncTankPhysCannister, CFuncTank );
+ DECLARE_DATADESC();
+
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
+
+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::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
+{
+ // Find our barrel volume
+ if ( !m_hBarrelVolume )
+ {
+ 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()) );
+ return;
+ }
+ }
+
+ // 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 );
+}
+
+//=========================================================
+//=========================================================
+static const char *s_pUpdateBeamThinkContext = "UpdateBeamThinkContext";
+#define COMBINE_CANNON_BEAM "effects/blueblacklargebeam.vmt"
+//#define COMBINE_CANNON_BEAM "sprites/strider_bluebeam.vmt"
+
+class CFuncTankCombineCannon : public CFuncTankGun
+{
+ DECLARE_CLASS( CFuncTankCombineCannon, CFuncTankGun );
+
+ void Precache();
+ void Spawn();
+ void CreateBeam();
+ void DestroyBeam();
+ void FuncTankPostThink();
+ void AdjustRateOfFire();
+ void UpdateBeamThink( void );
+ void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread );
+ void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
+ void TankDeactivate();
+
+ void InputSetTargetEntity( inputdata_t &inputdata );
+ void InputClearTargetEntity( inputdata_t &inputdata );
+
+ void InputEnableHarrass( inputdata_t &inputdata );
+ void InputDisableHarrass( inputdata_t &inputdata );
+
+ COutputEvent m_OnShotAtPlayer;
+
+ CHandle<CBeam> m_hBeam;
+
+ DECLARE_DATADESC();
+
+private:
+ float m_originalFireRate;
+ float m_flTimeNextSweep;
+ float m_flTimeBeamOn;
+ Vector m_vecTrueForward;
+ bool m_bShouldHarrass;
+ bool m_bLastTargetWasNPC; // Tells whether the last entity we fired a shot at was an NPC (otherwise it was the player)
+};
+
+BEGIN_DATADESC( CFuncTankCombineCannon )
+ DEFINE_FIELD( m_originalFireRate, FIELD_FLOAT ),
+ DEFINE_THINKFUNC( UpdateBeamThink ),
+ DEFINE_FIELD( m_flTimeNextSweep, FIELD_TIME ),
+ DEFINE_FIELD( m_flTimeBeamOn, FIELD_TIME ),
+ DEFINE_FIELD( m_hBeam, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_vecTrueForward, FIELD_VECTOR ),
+ DEFINE_FIELD( m_bShouldHarrass, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_bLastTargetWasNPC, FIELD_BOOLEAN ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableHarrass", InputEnableHarrass ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableHarrass", InputDisableHarrass ),
+
+ DEFINE_OUTPUT( m_OnShotAtPlayer, "OnShotAtPlayer" ),
+
+END_DATADESC()
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::Precache()
+{
+ m_originalFireRate = m_fireRate;
+
+ PrecacheModel(COMBINE_CANNON_BEAM);
+ PrecacheParticleSystem( "Weapon_Combine_Ion_Cannon" );
+
+ BaseClass::Precache();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::Spawn()
+{
+ BaseClass::Spawn();
+ m_flTimeBeamOn = gpGlobals->curtime;
+ CreateBeam();
+
+ m_bShouldHarrass = true;
+
+ GetVectors( &m_vecTrueForward, NULL, NULL );
+ m_bLastTargetWasNPC = false;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::CreateBeam()
+{
+ if (!m_hBeam && gpGlobals->curtime >= m_flTimeBeamOn )
+ {
+ m_hBeam = CBeam::BeamCreate( COMBINE_CANNON_BEAM, 1.0f );
+ m_hBeam->SetColor( 255, 255, 255 );
+ SetContextThink( &CFuncTankCombineCannon::UpdateBeamThink, gpGlobals->curtime, s_pUpdateBeamThinkContext );
+ }
+ else
+ {
+ // Beam seems to be on, or I'm not supposed to have it on at the moment.
+ return;
+ }
+
+ Vector vecInitialAim;
+
+ AngleVectors( GetAbsAngles(), &vecInitialAim, NULL, NULL );
+
+ m_hBeam->PointsInit( WorldBarrelPosition(), WorldBarrelPosition() + vecInitialAim );
+ m_hBeam->SetBrightness( 255 );
+ m_hBeam->SetNoise( 0 );
+ m_hBeam->SetWidth( 3.0f );
+ m_hBeam->SetEndWidth( 0 );
+ m_hBeam->SetScrollRate( 0 );
+ m_hBeam->SetFadeLength( 60 ); // five feet to fade out
+ //m_hBeam->SetHaloTexture( sHaloSprite );
+ m_hBeam->SetHaloScale( 4.0f );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::DestroyBeam()
+{
+ if( m_hBeam )
+ {
+ UTIL_Remove( m_hBeam );
+ m_hBeam.Set(NULL);
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::AdjustRateOfFire()
+{
+ // Maintain 1.5 rounds per second rate of fire.
+ m_fireRate = 1.5;
+/*
+ if( m_hTarget.Get() != NULL && m_hTarget->IsPlayer() )
+ {
+ if( m_bLastTargetWasNPC )
+ {
+ // Cheat, and be able to fire RIGHT NOW if the target is a player and the
+ // last target I fired at was an NPC. This prevents the player from running
+ // for it while the gun is busy dealing with NPCs
+ SetNextAttack( gpGlobals->curtime );
+ }
+ }
+*/
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+#define COMBINE_CANNON_BEAM_MAX_DIST 1900.0f
+void CFuncTankCombineCannon::UpdateBeamThink()
+{
+ SetContextThink( &CFuncTankCombineCannon::UpdateBeamThink, gpGlobals->curtime + 0.025, s_pUpdateBeamThinkContext );
+
+ // Always try to create the beam.
+ CreateBeam();
+
+ if( !m_hBeam )
+ return;
+
+ trace_t trBeam;
+ trace_t trShot;
+ trace_t trBlockLOS;
+
+ Vector vecBarrel = WorldBarrelPosition();
+ Vector vecAim;
+ AngleVectors( GetAbsAngles(), &vecAim, NULL, NULL );
+
+ AI_TraceLine( vecBarrel, vecBarrel + vecAim * COMBINE_CANNON_BEAM_MAX_DIST, MASK_SHOT, this, COLLISION_GROUP_NONE, &trBeam );
+
+ m_hBeam->SetStartPos( trBeam.startpos );
+ m_hBeam->SetEndPos( trBeam.endpos );
+
+ if( !(m_spawnflags & SF_TANK_AIM_AT_POS) )
+ {
+ SetTargetPosition( trBeam.endpos );
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::FuncTankPostThink()
+{
+ AdjustRateOfFire();
+
+ if( m_hTarget.Get() == NULL )
+ {
+ if( gpGlobals->curtime > m_flTimeNextSweep )
+ {
+ AddSpawnFlags( SF_TANK_AIM_AT_POS );
+
+ Vector vecTargetPosition = GetTargetPosition();
+ CBasePlayer *pPlayer = AI_GetSinglePlayer();
+ Vector vecToPlayer = pPlayer->WorldSpaceCenter() - GetAbsOrigin();
+ vecToPlayer.NormalizeInPlace();
+
+ bool bHarass = false;
+ float flDot = DotProduct( m_vecTrueForward, vecToPlayer );
+
+ if( flDot >= 0.9f && m_bShouldHarrass )
+ {
+ //Msg("%s Harrassing player\n", GetDebugName() );
+ vecTargetPosition = pPlayer->EyePosition();
+ bHarass = true;
+ }
+ else
+ {
+ //Msg( "%s Bored\n", GetDebugName() );
+ // Just point off in the distance, more or less directly ahead of me.
+ vecTargetPosition = GetAbsOrigin() + m_vecTrueForward * 1900.0f;
+ }
+
+ int i;
+ Vector vecTest;
+ bool bFoundPoint = false;
+ for( i = 0 ; i < 5 ; i++ )
+ {
+ vecTest = vecTargetPosition;
+
+ if( bHarass )
+ {
+ vecTest.x += random->RandomFloat( -48, 48 );
+ vecTest.y += random->RandomFloat( -48, 48 );
+ vecTest.z += random->RandomFloat( 16, 48 );
+ }
+ else
+ {
+ vecTest.x += random->RandomFloat( -48, 48 );
+ vecTest.y += random->RandomFloat( -48, 48 );
+ vecTest.z += random->RandomFloat( -48, 48 );
+ }
+
+ // Get the barrel position
+ Vector vecBarrelEnd = WorldBarrelPosition();
+ trace_t trLOS;
+ trace_t trShoot;
+
+ // Ignore the func_tank and any prop it's parented to, and check line of sight to the point
+ // Trace to the point. If an opaque trace doesn't reach the point, that means the beam hit
+ // something closer, (including a blockLOS), so try again.
+ CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE );
+ AI_TraceLine( vecBarrelEnd, vecTest, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &trLOS );
+ AI_TraceLine( vecBarrelEnd, vecTest, MASK_SHOT, &traceFilter, &trShoot );
+
+ if( trLOS.fraction < trShoot.fraction )
+ {
+ // Damn block LOS brushes.
+ continue;
+ }
+
+ //Msg("Point is visible in %d tries\n", i);
+ bFoundPoint = true;
+ break;
+ }
+
+ if( bFoundPoint )
+ {
+ vecTargetPosition = vecTest;
+ SetTargetPosition( vecTargetPosition );
+ //Msg("New place\n");
+ }
+
+ if( bHarass )
+ {
+ m_flTimeNextSweep = gpGlobals->curtime + random->RandomFloat( 0.25f, 0.75f );
+ }
+ else
+ {
+ m_flTimeNextSweep = gpGlobals->curtime + random->RandomFloat( 1, 3 );
+ }
+ }
+ }
+ else
+ {
+ //Msg("%d engaging: %s\n", entindex(), m_hTarget->GetClassname() );
+ RemoveSpawnFlags( SF_TANK_AIM_AT_POS );
+ }
+}
+
+//---------------------------------------------------------
+// A normal func_tank uses a method of aiming the gun that will
+// always follow a fast-moving player. This is because the func_tank
+// turns the weapon by applying angular velocities in the early
+// phase of the func_tank's Think(). Because the bullet is fired
+// later in the same think, it is fired before the game physics have
+// updated the func_tank's angles using the newly-computed angular
+// velocity, so the bullet always trails the target slightly.
+// This is unacceptable for the Combine Cannon, as the cannon MUST
+// strike a moving player with absolute certainty. As a quick
+// remedy, this code allows the combine cannon to fire a bullet
+// at a slightly different angle than the gun is aiming, to
+// ensure a hit. Large discrepancies are ignored and we accept
+// the miss instead of presenting a bullet fired at an obviously
+// adjusted angle.
+//---------------------------------------------------------
+void CFuncTankCombineCannon::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread )
+{
+ // Specifically do NOT fire in aim at pos mode. This is just for show.
+ if( HasSpawnFlags(SF_TANK_AIM_AT_POS) )
+ return;
+
+ Vector vecAdjustedForward = forward;
+
+ if( m_hTarget != NULL )
+ {
+ Vector vecToTarget = m_hTarget->BodyTarget( barrelEnd, false ) - barrelEnd;
+ VectorNormalize( vecToTarget );
+
+ float flDot = DotProduct( vecToTarget, forward );
+
+ if( flDot >= 0.97 )
+ {
+ vecAdjustedForward = vecToTarget;
+ }
+
+ if( m_hTarget->IsNPC() )
+ m_bLastTargetWasNPC = true;
+ else
+ m_bLastTargetWasNPC = false;
+
+ if( m_hTarget->IsPlayer() )
+ m_OnShotAtPlayer.FireOutput( this, this );
+ }
+
+ BaseClass::Fire( bulletCount, barrelEnd, vecAdjustedForward, pAttacker, bIgnoreSpread );
+
+ // Turn off the beam and tell it to stay off for a bit. We want it to look like the beam became the
+ // ion cannon 'rail gun' effect.
+ DestroyBeam();
+ m_flTimeBeamOn = gpGlobals->curtime + 0.2f;
+
+ m_flTimeNextSweep = gpGlobals->curtime + random->RandomInt( 1.0f, 2.0f );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
+{
+ // If the shot passed near the player, shake the screen.
+ if( AI_IsSinglePlayer() )
+ {
+ Vector vecPlayer = AI_GetSinglePlayer()->EyePosition();
+
+ Vector vecNearestPoint = PointOnLineNearestPoint( vecTracerSrc, tr.endpos, vecPlayer );
+
+ float flDist = vecPlayer.DistTo( vecNearestPoint );
+
+ if( flDist >= 10.0f && flDist <= 120.0f )
+ {
+ // Don't shake the screen if we're hit (within 10 inches), but do shake if a shot otherwise comes within 10 feet.
+ UTIL_ScreenShake( vecNearestPoint, 10, 60, 0.3, 120.0f, SHAKE_START, false );
+ }
+ }
+
+ // Send the railgun effect
+ DispatchParticleEffect( "Weapon_Combine_Ion_Cannon", vecTracerSrc, tr.endpos, vec3_angle, NULL );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::TankDeactivate()
+{
+ DestroyBeam();
+ m_flTimeBeamOn = gpGlobals->curtime + 1.0f;
+ SetContextThink( NULL, 0, s_pUpdateBeamThinkContext );
+
+ BaseClass::TankDeactivate();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::InputSetTargetEntity( inputdata_t &inputdata )
+{
+ BaseClass::InputSetTargetEntity( inputdata );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::InputClearTargetEntity( inputdata_t &inputdata )
+{
+/*
+ m_targetEntityName = NULL_STRING;
+ m_hTarget = NULL;
+
+ // No longer aim at target position if have one
+ m_spawnflags &= ~SF_TANK_AIM_AT_POS;
+*/
+ BaseClass::InputClearTargetEntity( inputdata );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::InputEnableHarrass( inputdata_t &inputdata )
+{
+ m_bShouldHarrass = true;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CFuncTankCombineCannon::InputDisableHarrass( inputdata_t &inputdata )
+{
+ m_bShouldHarrass = false;
+}
+
+
+LINK_ENTITY_TO_CLASS( func_tank_combine_cannon, CFuncTankCombineCannon );
|