From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/hl2/func_tank.cpp | 8862 +++++++++++++++++----------------- 1 file changed, 4431 insertions(+), 4431 deletions(-) (limited to 'mp/src/game/server/hl2/func_tank.cpp') 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(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( 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( 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( 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( 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( 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( 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( 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(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 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( 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( 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 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 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( 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 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(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( 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( 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( 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( 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( 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( 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( 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(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 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( 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( 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 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 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( 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 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 ); -- cgit v1.2.3