diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hl2/func_tank.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/hl2/func_tank.cpp')
| -rw-r--r-- | mp/src/game/server/hl2/func_tank.cpp | 8862 |
1 files changed, 4431 insertions, 4431 deletions
diff --git a/mp/src/game/server/hl2/func_tank.cpp b/mp/src/game/server/hl2/func_tank.cpp index b41708e9..db5a2e32 100644 --- a/mp/src/game/server/hl2/func_tank.cpp +++ b/mp/src/game/server/hl2/func_tank.cpp @@ -1,4431 +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 );
+//========= 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 ); |